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

feat(resolution): Expand media query support #372

Merged
merged 1 commit into from
Feb 19, 2018
Merged
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
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}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about renaming show to showWhen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to make changes to the resolution api now is the time (I think only PES is using it)

Since this is part of the existing API, if we change it I wouldn't make it as part of this PR but we can update it

<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