Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds integration test for combined up/down responsive props #198

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cypress/integration/responsive-props/down.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
describe('Behavior: "down"', () => {
before(() => {
cy.loadStory(['core'], ['responsive-props', 'down'])
})

it('with multiple "down" props', () => {
const assertPadding = (values) => {
return cy.get('#composition-down').should('have.css', 'padding', values)
}

assertPadding('10px')
cy.setBreakpoint('sm').then(() => assertPadding('20px'))
cy.setBreakpoint('md').then(() => assertPadding('30px'))
cy.setBreakpoint('lg').then(() => assertPadding('40px'))
cy.setBreakpoint('xl').then(() => assertPadding('50px'))
})

it('when combined with "up" sibling', () => {
const assertTemplateCols = (value) => {
return cy
.get('#composition-combination')
.should('have.css', 'grid-template-columns', value)
}

assertTemplateCols('200px')
cy.setBreakpoint('sm').then(() => assertTemplateCols('100px'))
cy.setBreakpoint('md').then(() => assertTemplateCols('200px'))
cy.setBreakpoint('lg').then(() => assertTemplateCols('200px'))
cy.setBreakpoint('xl').then(() => assertTemplateCols('200px'))
})
})
1 change: 1 addition & 0 deletions cypress/integration/responsive-props/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ describe('Responsive props', () => {
require('./mobile-first.test')
require('./breakpoint-specific.test')
require('./bell-notch.test')
require('./down.test')
})
5 changes: 1 addition & 4 deletions examples/Core/ResponsiveProps/BreakpointSpecific.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react'
import Layout, { Composition } from 'atomic-layout'
// import resetLayout from '../resetLayout'
import { Composition } from 'atomic-layout'
import Square from '@stories/Square'

// resetLayout(Layout)

const BreakpointSpecific = () => (
<Composition
id="composition"
Expand Down
31 changes: 31 additions & 0 deletions examples/Core/ResponsiveProps/Down.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import { Composition } from 'atomic-layout'
import Square from '@stories/Square'

const DownResponsiveProps = () => (
<>
{/* Multiple "down" behaviors */}
<Composition
id="composition-down"
padding={10}
paddingSmDown={20}
paddingMdDown={30}
paddingLgDown={40}
paddingXlDown={50}
>
<Square>First</Square>
</Composition>

{/* Combination of "up" and "down" */}
{/* <Composition
id="composition-combination"
templateCols="200px"
templateColsSmDown="100px"
gutter={10}
>
<Square>First</Square>
<Square>Second</Square>
</Composition> */}
</>
)
export default DownResponsiveProps
5 changes: 1 addition & 4 deletions examples/Core/ResponsiveProps/InclusiveNotch.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react'
import Layout, { Composition } from 'atomic-layout'
// import resetLayout from '../resetLayout'
import { Composition } from 'atomic-layout'
import Square from '@stories/Square'

// resetLayout(Layout)

const template = 'first second'

const InclusiveNotch = () => (
Expand Down
5 changes: 1 addition & 4 deletions examples/Core/ResponsiveProps/MobileFirst.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react'
import Layout, { Composition } from 'atomic-layout'
// import resetLayout from '../resetLayout'
import { Composition } from 'atomic-layout'
import Square from '@stories/Square'

// resetLayout(Layout)

const Foo = () => (
<Composition id="composition" areas="first second" gutter={10}>
{({ First, Second }) => (
Expand Down
2 changes: 2 additions & 0 deletions examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ storiesOf('Core|Rendering', module)
import MobileFirstResponsiveProps from './Core/ResponsiveProps/MobileFirst'
import BreakpointSpecificResponsiveProps from './Core/ResponsiveProps/BreakpointSpecific'
import InclusiveNotchResponsiveProps from './Core/ResponsiveProps/InclusiveNotch'
import DownResponsiveProps from './Core/ResponsiveProps/Down'

storiesOf('Core|Responsive props', module)
.add('Mobile-first', () => <MobileFirstResponsiveProps />)
.add('Breakpoint-specific', () => <BreakpointSpecificResponsiveProps />)
.add('Inclusive-notch', () => <InclusiveNotchResponsiveProps />)
.add('Down', () => <DownResponsiveProps />)

/**
* Configuration
Expand Down
4 changes: 2 additions & 2 deletions src/utils/breakpoints/closeBreakpoint/closeBreakpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import getPrefix from '@utils/strings/getPrefix'
* of the original breakpoint are omitted.
*
* @example
* flipBreakpoint({ minWidth: 500, maxWidth: 600 })
* closeBreakpoint({ minWidth: 500, maxWidth: 600 })
* // { maxWidth: 499 }
*/
export default function flipBreakpoint(breakpoint: Breakpoint): Breakpoint {
export default function closeBreakpoint(breakpoint: Breakpoint): Breakpoint {
return Object.entries(breakpoint)
.map(([propName, propValue]) => [getPrefix(propName), propName, propValue])
.filter(([prefix]) => prefix !== 'max')
Expand Down
157 changes: 122 additions & 35 deletions src/utils/styles/applyStyles/applyStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,144 @@ import { BreakpointBehavior } from '@const/defaultOptions'
import propAliases from '@const/propAliases'
import parsePropName, { Props } from '@utils/strings/parsePropName'
import isset from '@utils/functions/isset'
import mergeBreakpoints from '@utils/breakpoints/mergeBreakpoints'
import createMediaQuery from '../createMediaQuery'
import { AreaBreakpoint } from '@src/utils/breakpoints/getAreaBreakpoints'

const createStyleString = (
propsList: string[],
propValue: any,
breakpoint: any,
behavior: BreakpointBehavior,
areaBreakpoint: {
isDefault: boolean
values: AreaBreakpoint
},
// breakpoint: any,
// behavior: BreakpointBehavior,
) => {
const { isDefault, values } = areaBreakpoint
const { behavior, ...breakpoint } = values
const styleProps = propsList
.map((propName) => `${propName}:${String(propValue)};`)
.join('')

const breakpointOptions = Layout.breakpoints[breakpoint.name]
console.warn('createStyleString')
console.log({ areaBreakpoint })
console.log({ styleProps })

/**
* Wrap CSS rule in a media query only if its prop includes
* a breakpoint and behavior different than the default ones.
*/
// const breakpointOptions = Layout.breakpoints[breakpoint.name]

// Wrap CSS rule in a media query only if its prop includes
// a breakpoint and behavior different than the default ones.
const shouldWrapInMediaQuery =
breakpointOptions &&
!(breakpoint.isDefault && behavior === Layout.defaultBehavior)
!!areaBreakpoint && !(isDefault && behavior === Layout.defaultBehavior)

console.log('has areaBreakpoint', !!areaBreakpoint)
console.log('isDefault?', isDefault)
console.log('has default beheavior', behavior === Layout.defaultBehavior)
console.log({ shouldWrapInMediaQuery })

if (shouldWrapInMediaQuery) {
console.log('SHOULD wrap in media query...')
const queryString = createMediaQuery(breakpoint, behavior)
console.log('given this breakpoint:', breakpoint)
console.log('given behavior:', behavior)
console.log({ queryString })
return `@media ${queryString} {${styleProps}}`
}

return shouldWrapInMediaQuery
? `@media ${createMediaQuery(breakpointOptions, behavior)} {${styleProps}}`
: styleProps
return styleProps
}

export default function applyStyles(pristineProps: Props): string {
return (
Object.keys(pristineProps)
/* Parse each prop to include "breakpoint" and "behavior" */
.map(parsePropName)
/* Filter out props that are not included in prop aliases */
.filter(({ purePropName }) => propAliases.hasOwnProperty(purePropName))
/* Filter out props with "undefined" or "null" as value */
.filter(({ originPropName }) => isset(pristineProps[originPropName]))
/* Map each prop to a CSS string */
.map(({ purePropName, originPropName, breakpoint, behavior }) => {
const { props, transformValue } = propAliases[purePropName]
const propValue = pristineProps[originPropName]
const transformedPropValue = transformValue
? transformValue(propValue)
: propValue

return createStyleString(
props,
transformedPropValue,
breakpoint,
behavior,
console.log('applyStyles for', pristineProps)

const propsGroups = Object.keys(pristineProps)
/* Filter out props with "undefined" or "null" as value */
.filter((propName) => isset(pristineProps[propName]))
/* Parse each prop to include "breakpoint" and "behavior" */
.map(parsePropName)
/* Filter out props that are not included in prop aliases */
.filter(({ purePropName }) => propAliases.hasOwnProperty(purePropName))
/* Map each prop to a CSS string */
.reduce((groups, parsedPropName) => {
const { purePropName } = parsedPropName
const nextGroupEntry = groups[purePropName]
? groups[purePropName].concat(parsedPropName)
: [parsedPropName]

return {
...groups,
[purePropName]: nextGroupEntry,
}
}, {})

console.log(propsGroups)

// This can be moved outside
const createStyle = (parsedPropName) => {
const { originPropName, purePropName, breakpoint } = parsedPropName
const { props, transformValue } = propAliases[purePropName]
const propValue = pristineProps[originPropName]
const transformedPropValue = transformValue
? transformValue(propValue)
: propValue

return createStyleString(props, transformedPropValue, breakpoint)
}

const styles = Object.keys(propsGroups).reduce((acc, propName) => {
const declarations = propsGroups[propName]
const nextStyles = declarations.map((parsedPropName, index) => {
const { behavior, breakpoint } = parsedPropName
const goesDown = behavior === 'down'
const prevDeclaration = declarations[index - 1]

// Prevent multiple "down" responsive props
// from overlapping.
if (goesDown && prevDeclaration && prevDeclaration.behavior === 'down') {
const actualBreakpoint = mergeBreakpoints(
{
...Layout.breakpoints[breakpoint.name],
behavior,
},
{
...Layout.breakpoints[prevDeclaration.breakpoint.name],
behavior: 'up',
},
true,
)

// console.warn('combined down props!')
// console.log(
// `prop ${parsedPropName.originPropName} has a predcessor ${prevDeclaration.originPropName}`,
// )
// console.log({ actualBreakpoint })

return createStyle({
...parsedPropName,
breakpoint: {
...breakpoint,
behavior,
values: actualBreakpoint,
},
})
}

return createStyle({
...parsedPropName,
breakpoint: {
...breakpoint,
behavior,
values: Layout.breakpoints[breakpoint.name],
},
})
.join(' ')
)
})

return acc.concat(nextStyles)
}, [])
// .join(' ')

console.log({ styles })

return styles
}