Skip to content

Commit

Permalink
Adds support for "grid-template" prop
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed May 18, 2019
1 parent 81eac7c commit 1f3b32e
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 46 deletions.
44 changes: 44 additions & 0 deletions cypress/integration/recipes/grid-template.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
describe('Grid template', function() {
before(() => {
cy.loadStory(['recipes'], ['all', 'grid-template'])
})

beforeEach(() => {
cy.setBreakpoint('xs')
})

it('areas do not intersect', () => {
cy.get('#header').notIntersectWith('#content')
cy.setBreakpoint('md').then(() => {
cy.get('#header').notIntersectWith('#content')
})
})

describe('row', () => {
it('supporst exact row height', () => {
cy.get('#header').should('have.css', 'height', '100px')
cy.setBreakpoint('md').then(() => {
cy.get('#header').should('have.css', 'height', '500px')
cy.get('#content').should('have.css', 'height', '500px')
})
})

it('supports dynamic row height', () => {
cy.get('#content').should('have.css', 'height', '60px')
})
})

describe('column', () => {
it('supports exact column width', () => {
cy.setBreakpoint('md').then(() => {
cy.get('#header').should('have.css', 'width', '200px')
})
})

it('supports dynamic column width', () => {
cy.setBreakpoint('md').then(() => {
cy.get('#content').should('have.css', 'width', '543px')
})
})
})
})
1 change: 1 addition & 0 deletions cypress/integration/recipes/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
describe('Recipes', function() {
require('./iterative-areas.spec')
require('./grid-template.spec')
})
35 changes: 35 additions & 0 deletions examples/Recipes/GridTemplate.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import { Composition } from 'atomic-layout'
import Square from '@stories/Square'

const templateMobile = `
header 100px
content 1fr
`

const templateTablet = `
header content 500px
/ 200px 1fr
`

const List = () => (
<Composition
id="composition"
template={templateMobile}
templateMd={templateTablet}
gutter={10}
>
{({ Header, Content }) => (
<>
<Header id="header">
<Square>Header</Square>
</Header>
<Content id="content">
<Square>Content</Square>
</Content>
</>
)}
</Composition>
)

export default List
7 changes: 4 additions & 3 deletions examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ storiesOf('Hooks', module)
* Recipes
*/
import IterativeAreas from './Recipes/IterativeAreas'
import GridTemplate from './Recipes/GridTemplate'

storiesOf('Recipes|All', module).add('Iterative areas', () => (
<IterativeAreas />
))
storiesOf('Recipes|All', module)
.add('Iterative areas', () => <IterativeAreas />)
.add('Grid template', () => <GridTemplate />)

/**
* Bugfixes
Expand Down
2 changes: 1 addition & 1 deletion src/const/propAliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const propAliases: PropAliases = {
transformValue: transformTemplateString,
},
template: {
props: ['grid-template-areas'],
props: ['grid-template'],
transformValue: transformTemplateString,
},
templateCols: {
Expand Down
6 changes: 3 additions & 3 deletions src/const/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@ export interface GridProps extends GenericProps {
*/
areas?: string
/**
* Grid areas.
* @css `grid-template-areas`
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas
* Grid template that describes rows, columns and areas.
* @css `grid-template`
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template
*/
template?: string
/**
Expand Down
1 change: 1 addition & 0 deletions src/utils/strings/isAreaName/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './isAreaName'
20 changes: 20 additions & 0 deletions src/utils/strings/isAreaName/isAreaName.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import isAreaName from './isAreaName'

describe('isAreaName', () => {
describe('returns true', () => {
it('when given area name', () => {
expect(isAreaName('footer')).toBe(true)
})
})

describe('returns false', () => {
it('when given numeric value', () => {
expect(isAreaName('100px')).toBe(false)
expect(isAreaName('2fr')).toBe(false)
})

it('when given dimensional keyword', () => {
expect(isAreaName('auto')).toBe(false)
})
})
})
10 changes: 10 additions & 0 deletions src/utils/strings/isAreaName/isAreaName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const keywords = ['/', 'auto']

// Determines if the given string is a valid area name.
// Takes into account on the row/column dimensions and
// keywords in the "grid-template" definition.
export default function isAreaName(areaName: string): boolean {
const startsWithNumber = /^[0-9]/.test(areaName)
const isKeyword = keywords.includes(areaName)
return !startsWithNumber && !isKeyword
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@ import sanitizeTemplateArea from './sanitizeTemplateArea'

describe('sanitizeTemplateArea', () => {
it('enforces single quotes for new lines', () => {
const sanitized = sanitizeTemplateArea('foo')
expect(sanitized).toEqual("'foo'")
expect(sanitizeTemplateArea('foo')).toEqual(`'foo'`)
expect(sanitizeTemplateArea('foo bar')).toEqual(`'foo bar'`)
})

it('deduplicates single quotes', () => {
expect(sanitizeTemplateArea("'foo'")).toEqual(`'foo'`)

const multilineSanitized = sanitizeTemplateArea('foo bar')
expect(multilineSanitized).toEqual("'foo bar'")
expect(sanitizeTemplateArea("'foo bar'")).toEqual(`'foo bar'`)
})

it('removes duplicate single quotes', () => {
const sanitized = sanitizeTemplateArea("'foo'")
expect(sanitized).toEqual("'foo'")
describe('when given "grid-template" syntax', () => {
it('supports single area definition', () => {
expect(sanitizeTemplateArea('foo bar 100px')).toEqual(`'foo bar' 100px`)
})

it('supports multi-area definition', () => {
expect(sanitizeTemplateArea('first second 2fr')).toEqual(
`'first second' 2fr`,
)
})

const multilineSanitized = sanitizeTemplateArea("'foo bar'")
expect(multilineSanitized).toEqual("'foo bar'")
it('returns "grid-template-columns" dimensions without quoutes', () => {
expect(sanitizeTemplateArea('/ 200px auto 1fr')).toEqual(
`/ 200px auto 1fr`,
)
})
})
})
36 changes: 29 additions & 7 deletions src/utils/strings/sanitizeTemplateArea/sanitizeTemplateArea.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import compose from '@utils/functions/compose'
import isAreaName from '../isAreaName'

type SanitizeTemplateArea = (str: string) => string

/**
* Trims whitespace, removes duplicate single quotes and enforces
* that area line is wrapped in single quotes.
*/
// Joins a given template string fragments into a valid template string.
// Takes into account proper single quote wrapping around the areas
// and no single quotes around the dimensions (row/columns).
const joinTemplateFragments = (fragments: string[]): string => {
const areas = []
const suffixes = []

fragments.forEach((areaName) => {
if (isAreaName(areaName)) {
areas.push(areaName)
} else {
suffixes.push(areaName)
}
})

const joinedAreas = areas.length > 0 ? `'${areas.join(' ')}'` : ''
const joinedSuffixes = suffixes.join(' ')

return [joinedAreas, joinedSuffixes].filter(Boolean).join(' ')
}

// Prepares given "grid-template-areas" string to be digestible.
// Trims whitespace, deduplicates single quotes and wraps
// each line in single quotes.
const sanitizeTemplateArea: SanitizeTemplateArea = compose(
(area: string) => area.replace(/^.+$/gm, (areaName) => `'${areaName}'`),
(area: string) => area.replace(/'+/gm, ''),
(area: string) => area.trim(),
joinTemplateFragments,
(area: string): string[] => area.split(' '),
(area: string): string => area.replace(/'+/gm, ''),
(area: string): string => area.trim(),
)

export default sanitizeTemplateArea
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,38 @@ import sanitizeTemplateString from './sanitizeTemplateString'

describe('sanitizeTemplateString', () => {
it('sanitizes string with quotes', () => {
const sanitized = sanitizeTemplateString(`
'header header'
'content aside'
'footer footer'
const areas = sanitizeTemplateString(`
header header
content aside
footer footer
`)
expect(sanitized).toEqual(['aside', 'content', 'footer', 'header'])
expect(areas).toEqual(['aside', 'content', 'footer', 'header'])
})

it('sanitizes string without quotes', () => {
const sanitized = sanitizeTemplateString(`
const areas = sanitizeTemplateString(`
first first
second third
fourth fourth
`)
expect(sanitized).toEqual(['first', 'fourth', 'second', 'third'])
expect(areas).toEqual(['first', 'fourth', 'second', 'third'])
})

it('sanitizes string without indentation', () => {
const sanitized = sanitizeTemplateString(`
const areas = sanitizeTemplateString(`
first first
second third
`)
expect(sanitized).toEqual(['first', 'second', 'third'])
expect(areas).toEqual(['first', 'second', 'third'])
})

it('sanitizes "grid-template" string', () => {
const areas = sanitizeTemplateString(`
200px 1fr
header header 100px
content aside auto
`)

expect(areas).toEqual(['aside', 'content', 'header'])
})
})
28 changes: 15 additions & 13 deletions src/utils/strings/sanitizeTemplateString/sanitizeTemplateString.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
import compose from '@utils/functions/compose'
import isAreaName from '../isAreaName'

type SanitizeTemplateString = (str: string) => string[]

/**
* Returns an array of unique normalized grid areas
* from the given template string.
*/
// Returns an array of unique normalized grid areas
// from the given template string.
const sanitizeTemplateString: SanitizeTemplateString = compose(
(list: string[]): string[] => list.sort(),

/* Deduplicates area strings */
// Deduplicate area strings
(list: string[]): string[] => Array.from(new Set(list)),

/* Filters out empty area strings */
(arr: any[]) => arr.filter(Boolean),
// Filter out "template" row/columns sizes
(arr: string[]): string[] => arr.filter(isAreaName),

/* Splits into a list of areas */
(str: string) => str.split(' '),
// Filter out empty area strings
(arr: string[]): string[] => arr.filter(Boolean),

/* Deduplicates multiple spaces */
(str: string) => str.replace(/\s+/g, ' '),
// Split into a list of areas
(str: string): string[] => str.split(' '),

/* Replaces newlines and single quotes with spaces */
(str: string) => str.replace(/\r?\n|\'+/g, ' '),
// Deduplicate multiple spaces
(str: string): string => str.replace(/\s+/g, ' '),

// Replace new lines and single quotes with spaces
(str: string): string => str.replace(/\r?\n|\'+/g, ' '),
)

export default sanitizeTemplateString
4 changes: 3 additions & 1 deletion src/utils/strings/toDashedString/toDashedString.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* Converts given cammelCase string into kebab-case.
* @example
* toDashedString('fooBar') // "foo-bar"
* toDashedString('fooBar')
* @returns "foo-bar"
*/
export default function toDashedString(str: string): string {
return str.replace(/[A-Z]/g, (capitalLetter) => {
Expand Down

0 comments on commit 1f3b32e

Please sign in to comment.