Skip to content

Commit

Permalink
feat(resolution): Expand media query support (#372)
Browse files Browse the repository at this point in the history
- Lower the min width to account for iPhone 5 resolution
- Add support for OR logic within displayAliases
  - This is to support applying a media query when either the screen or the device
    with are within certain values

BREAKING CHANGE layout defaults change
  • Loading branch information
obartra authored Feb 19, 2018
1 parent 3f8386c commit 32eb367
Show file tree
Hide file tree
Showing 50 changed files with 567 additions and 353 deletions.
2 changes: 1 addition & 1 deletion src/cxs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import cxs from 'cxs/monolithic'

cxs.prefix('xnr_')
cxs.prefix('gym_')

export default cxs
44 changes: 30 additions & 14 deletions src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,40 @@ export default {
columns: 12, // number of columns used in the layout
displayAliases: {
// aliases used for the different display breakpoints:
small: {
// - "small" alias used when width is less than 600px
maxWidth: '599px',
},
medium: {
// - "medium" alias used when width is between 600px and 900px
minWidth: '600px',
maxWidth: '899px',
},
large: {
// - "large" alias used when width is equal or greater than 900px
minWidth: '900px',
},
small: [
{
// - "small" alias used when width is less than 600px
maxWidth: '599px',
},
{
maxDeviceWidth: '599px',
},
],
medium: [
{
// - "medium" alias used when width is between 600px and 900px
minWidth: '600px',
maxWidth: '899px',
},
{
minDeviceWidth: '600px',
maxDeviceWidth: '899px',
},
],
large: [
{
// - "large" alias used when width is equal or greater than 900px
minWidth: '900px',
},
{
minDeviceWidth: '900px',
},
],
},
fallbackDisplayKey: 'default', // key to use when a display alias is omitted or non matching
gutter: 3, // value (in base units) that separates columns horizontally
maxPageWidth: 153, // maximum page width (in base units) 153 * base (8px) = 1224px
minPageWidth: 50, // minimum page width (in base units) 50 * base (8px) = 400px
minPageWidth: 40, // minimum page width (in base units) 40 * base (8px) = 320px = iPhone5 screen width
pageMargin: {
// page margins (in base units) for each display breakpoint
small: 1,
Expand Down
4 changes: 2 additions & 2 deletions src/dev/dev.styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ function aliasMarginQuery(query, padding) {
}
return {
leftMargin: {
[`@media screen and ${query}`]: {
[query]: {
width: `${padding}px`,
},
},
rightMargin: {
[`@media screen and ${query}`]: {
[query]: {
width: `${padding}px`,
},
},
Expand Down
5 changes: 0 additions & 5 deletions src/errors/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ const addError =
process.env.NODE_ENV === 'production' ? addProdError : addDevError
const errors = {}

addError(
errors,
'INVALIDMEDIAKEY',
`Specified query is invalid. Only the following keys are allowed: "minWidth", "maxWidth", "minHeight", "maxHeight", "aspectRatio" and "orientation".`
)
addError(
errors,
'INVALIDSPACING',
Expand Down
6 changes: 3 additions & 3 deletions src/root/root.styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const smallRoot = {
function addRootPadding(query, padding) {
return {
root: {
[`@media screen and ${query}`]: {
[query]: {
paddingLeft: padding,
paddingRight: padding,
},
Expand All @@ -24,10 +24,10 @@ function addRootPadding(query, padding) {
function addChildPadding(query, padding) {
return {
root: {
[`@media screen and ${query}`]: smallRoot,
[query]: smallRoot,
},
child: {
[`@media screen and ${query}`]: {
[query]: {
flexShrink: 0,
width: `calc(100% + ${padding}px)`,
},
Expand Down
14 changes: 4 additions & 10 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,12 @@ export type SpacingAliases = {
+[spacingAlias: string]: number,
}

type ResolutionKeys =
| 'minWidth'
| 'maxWidth'
| 'minHeight'
| 'maxHeight'
| 'aspectRatio'
| 'orientation'
type DisplayProperties = {
+[resolutionKey: string]: string,
}

export type DisplayAliases = {
+[displayAlias: string]: {
+[ResolutionKeys]: string,
},
+[displayAlias: string]: DisplayProperties | Array<DisplayProperties>,
}

export type ConfigProviderContext = {
Expand Down
4 changes: 4 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const splitPattern = /(?:(?:\s+)?,(?:\s+)?|\s+)/
export const noop: Noop = () => null
export const times = (n: number) =>
new Array(n).fill(undefined).map((val, index) => index)
export const kebabCase = (str: string) =>
str
.replace(/^[A-Z]/, match => match.toLowerCase())
.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)

export function validateSpacingProps(props: SpacingProps) {
if (process.env.NODE_ENV === 'production') {
Expand Down
25 changes: 25 additions & 0 deletions src/utils/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getCSS,
getValue,
getValues,
kebabCase,
parseSpacing,
replaceSpacingAliases,
toCXS,
Expand Down Expand Up @@ -353,3 +354,27 @@ describe('accumulateOver', () => {
})
})
})

describe('kebabCase', () => {
it('should work with empty strings', () => {
expect(kebabCase('')).toEqual('')
})

it('should not modify strings without upper case characters', () => {
const sample = 'this-is-a-test1#'

expect(kebabCase(sample)).toEqual(sample)
})

it('should lower case upper case letters and add a preceding dash', () => {
const sample = 'thisWillHaveDashes'

expect(kebabCase(sample)).toEqual('this-will-have-dashes')
})

it('should not add an additional dash if the first letter is capitalize', () => {
const sample = 'Lowercase'

expect(kebabCase(sample)).toEqual('lowercase')
})
})
44 changes: 18 additions & 26 deletions src/withResolution/withResolution.logic.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// @flow
import type { DisplayValues, DisplayAliases } from '../types'
import { splitPattern } from '../utils'
import log from '../log'
import errors from '../errors'
import { splitPattern, kebabCase } from '../utils'
import defaults from '../defaults'

export type ShouldShow = { [string]: boolean }
Expand Down Expand Up @@ -59,31 +57,25 @@ export function getSingleResolutionProps({
return propsCopy
}

const queriesMap = {
minWidth: 'min-width',
maxWidth: 'max-width',
minHeight: 'min-height',
maxHeight: 'max-height',
aspectRatio: 'aspect-ratio',
orientation: 'orientation',
}

export function getMediaQuery(
range: string,
displayAliases: DisplayAliases
displayAliases: DisplayAliases,
prefix: string = '@media '
): string {
const response = []
Object.keys(displayAliases[range]).forEach(key => {
if (key in queriesMap) {
const value = displayAliases[range][key]

response.push(`(${queriesMap[key]}: ${value})`)
} else {
log.error(errors.INVALIDMEDIAKEY, `"${key}" used`)
}
})

return response.join(' and ')
const displayPropertiesArray =
displayAliases[range] instanceof Array
? displayAliases[range]
: [displayAliases[range]]
const response = displayPropertiesArray
.map(
displayProperties =>
`${prefix}${Object.keys(displayProperties)
.map(key => `(${kebabCase(key)}: ${displayProperties[key]})`)
.join(' and ')}`
)
.join(', ')

return response
}

export function getMediaQueries(
Expand All @@ -94,7 +86,7 @@ export function getMediaQueries(

return showArray
.filter(range => range in displayAliases)
.map(range => [range, getMediaQuery(range, displayAliases)])
.map(range => [range, getMediaQuery(range, displayAliases, '')])
.reduce((acc, [range, query]) => {
if (query) {
return {
Expand Down
71 changes: 68 additions & 3 deletions src/withResolution/withResolution.logic.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
getMediaQueries,
getMediaQuery,
getSingleResolutionProps,
checkShouldShow,
} from './withResolution.logic'
Expand All @@ -16,6 +17,17 @@ describe('getMediaQueries', () => {
expect(out).toEqual({ test: '(min-width: 1px) and (max-width: 2px)' })
})

it('should return the same output when using array syntax', () => {
const test = {
minWidth: '1px',
maxWidth: '2px',
}
const out1 = getMediaQueries('test', { test })
const out2 = getMediaQueries('test', { test: [test] })

expect(out1).toEqual(out2)
})

it('should return a max value only when no min value is provided', () => {
const out = getMediaQueries('test', {
test: {
Expand All @@ -26,6 +38,21 @@ describe('getMediaQueries', () => {
expect(out).toEqual({ test: '(max-width: 2px)' })
})

it('should concatenate multiple queries with commas', () => {
const out = getMediaQueries('test', {
test: [
{
maxWidth: '2px',
},
{
minWidth: '1px',
},
],
})

expect(out).toEqual({ test: '(max-width: 2px), (min-width: 1px)' })
})

it('should return a min value only when no max value is provided', () => {
const out = getMediaQueries('test', {
test: {
Expand All @@ -36,14 +63,52 @@ describe('getMediaQueries', () => {
expect(out).toEqual({ test: '(min-width: 1px)' })
})

it('should return an empty string if an invalid value is passed', () => {
it('should kebab case keys', () => {
const out = getMediaQueries('test2', {
test: {
test2: {
MaxAspectRatio: '1/2',
},
})

expect(out).toEqual({ test2: '(max-aspect-ratio: 1/2)' })
})

it('should include invalid keys', () => {
const out = getMediaQueries('test2', {
test2: {
invalidValue: 'meow',
},
})

expect(out).toEqual({})
expect(out).toEqual({ test2: '(invalid-value: meow)' })
})
})

describe('getMediaQuery', () => {
let sampleDisplayAliases

beforeEach(() => {
sampleDisplayAliases = {
test: [{ something: '3px' }, { somethingElse: '4px' }],
}
})

it('should include the prefix on every response', () => {
const out = getMediaQuery('test', sampleDisplayAliases, 'prefix ')

expect(out).toBe('prefix (something: 3px), prefix (something-else: 4px)')
})

it('should default to "@media " prefix', () => {
const out = getMediaQuery('test', sampleDisplayAliases)

expect(out).toBe('@media (something: 3px), @media (something-else: 4px)')
})

it('should remove prefix when "" is set', () => {
const out = getMediaQuery('test', sampleDisplayAliases, '')

expect(out).toBe('(something: 3px), (something-else: 4px)')
})
})

Expand Down
Binary file modified storybook/stories/components/card/__screenshots__/base.spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified storybook/stories/components/card/__screenshots__/filter.spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified storybook/stories/components/header/__screenshots__/app.spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified storybook/stories/components/search/__screenshots__/nav.spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion storybook/stories/configProvider/columns.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { number } from '@storybook/addon-knobs'
import { ConfigProvider, Grid } from 'gymnast'
import { colors } from '../../shared'

export default function() {
export default () => {
const columns = number('Columns', 10, { range: true, min: 1, max: 24 })
const items = number('Items', 20, { range: true, min: 1, max: 48 })

Expand Down
38 changes: 38 additions & 0 deletions storybook/stories/configProvider/displayAliases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @flow
import * as React from 'react'
import { ConfigProvider, Grid } from 'gymnast'
import { colors } from '../../shared'

export default () => {
const displayAliases = {
test: {
minWidth: '351px',
maxWidth: '600px',
},
test2: [
{
maxWidth: '350px',
},
{
minWidth: '601px',
},
],
}

return (
<ConfigProvider displayAliases={displayAliases}>
<Grid justify="center">
<Grid show="test" style={colors.colors1} margin="L" size={6}>
<Grid justify="center" style={colors.colors2} padding="L">
I am only visible between 150-600px
</Grid>
</Grid>
<Grid show="test2" style={colors.colors2} margin="L" size={6}>
<Grid justify="center" style={colors.colors1} padding="L">
I am visible all other times!
</Grid>
</Grid>
</Grid>
</ConfigProvider>
)
}
Loading

0 comments on commit 32eb367

Please sign in to comment.