Skip to content

Commit

Permalink
feat(configprovider): Add fallbackDisplayKey prop (#350)
Browse files Browse the repository at this point in the history
- Add `fallbackDisplayKey` prop to `<ConfigProvider />`. Defaults
  to `default` but setting to another key (e.g. `large`) would use
  that as the default value

Closes #331
  • Loading branch information
obartra authored Dec 30, 2017
1 parent 93455d6 commit c3232d7
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 64 deletions.
2 changes: 2 additions & 0 deletions src/configProvider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Props = {
children?: React.Node,
columns?: number,
displayAliases?: DisplayAliases,
fallbackDisplayKey?: string,
gutter?: number,
maxPageWidth?: number,
minPageWidth?: number,
Expand All @@ -28,6 +29,7 @@ export const ConfigContextPropTypes = {
base: PropTypes.number,
columns: PropTypes.number,
displayAliases: PropTypes.shape({}),
fallbackDisplayKey: PropTypes.string,
gutter: PropTypes.number,
maxPageWidth: PropTypes.number,
minPageWidth: PropTypes.number,
Expand Down
21 changes: 14 additions & 7 deletions src/defaults.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
// @flow

export default {
base: 8,
columns: 12,
base: 8, // multiplier (in pixels) that all other size units use
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',
},
},
gutter: 3,
maxPageWidth: 153, // 153 * base (8px) = 1224px
minPageWidth: 50, // 50 * base (8px) = 400px
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
pageMargin: {
// page margins (in base units) for each display breakpoint
small: 1,
medium: 6,
large: 6,
},
spacingAliases: {
XS: 0.5,
// aliases used to indicate spacing values (margin/padding) in base
XS: 0.5, // units.
'S/2': 0.5,
S: 1,
'M/2': 1,
Expand All @@ -36,5 +43,5 @@ export default {
'2XL/2': 3,
'2XL': 6,
},
verticalGutter: 3,
verticalGutter: 3, // value (in base units) that separates columns vertically
}
1 change: 1 addition & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type ConfigProviderContext = {
+base?: number,
+columns?: number,
+displayAliases?: DisplayAliases,
+fallbackDisplayKey?: string,
+gutter?: number,
+maxPageWidth?: number,
+minPageWidth?: number,
Expand Down
11 changes: 6 additions & 5 deletions src/withResolution/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ export default function withResolution(
return null
}

const props = getSingleResolutionProps(
this.props,
this.state.shouldShow,
resolutionKeys
)
const props = getSingleResolutionProps({
props: this.props,
shouldShow: this.state.shouldShow,
resolutionKeys,
fallbackDisplayKey: getValue(this.context, 'fallbackDisplayKey'),
})

return <Component {...props} />
}
Expand Down
27 changes: 19 additions & 8 deletions src/withResolution/withResolution.logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DisplayValues, DisplayAliases } from '../types'
import { splitPattern } from '../utils'
import log from '../utils/log'
import errors from '../errors'
import defaults from '../defaults'

export type ShouldShow = { [string]: boolean }

Expand All @@ -14,10 +15,14 @@ function getActiveResolutionName(shouldShow: ShouldShow) {
return Object.keys(shouldShow).find(isTrue(shouldShow))
}

function extractObjectValue(value: any, shouldShow?: ShouldShow = {}) {
function extractObjectValue(
value: any,
shouldShow?: ShouldShow = {},
fallbackKey: string
) {
const active = getActiveResolutionName(shouldShow)

return active && active in value ? value[active] : value.default
return active && active in value ? value[active] : value[fallbackKey]
}

export function isObject(value: any) {
Expand All @@ -28,11 +33,17 @@ export function hasTrueValues(obj: {} = {}) {
return Object.keys(obj).some(isTrue(obj))
}

export function getSingleResolutionProps(
props: { show?: DisplayValues },
shouldShow?: ShouldShow,
resolutionKeys: Array<string> = []
) {
export function getSingleResolutionProps({
props,
shouldShow,
resolutionKeys = [],
fallbackDisplayKey = defaults.fallbackDisplayKey,
}: {|
+props: { show?: DisplayValues },
+shouldShow?: ShouldShow,
+resolutionKeys: Array<string>,
+fallbackDisplayKey: string,
|}) {
const { ...propsCopy } = props

delete propsCopy.show
Expand All @@ -41,7 +52,7 @@ export function getSingleResolutionProps(
const value = propsCopy[key]

if (isObject(value) && resolutionKeys.includes(key)) {
propsCopy[key] = extractObjectValue(value, shouldShow)
propsCopy[key] = extractObjectValue(value, shouldShow, fallbackDisplayKey)
}
})

Expand Down
142 changes: 142 additions & 0 deletions src/withResolution/withResolution.logic.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import {
getMediaQueries,
getSingleResolutionProps,
} from './withResolution.logic'

describe('getMediaQueries', () => {
it('should return a max and min value base on the available aliases', () => {
const out = getMediaQueries('test', {
test: {
minWidth: '1px',
maxWidth: '2px',
},
})

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

it('should return a max value only when no min value is provided', () => {
const out = getMediaQueries('test', {
test: {
maxWidth: '2px',
},
})

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

it('should return a min value only when no max value is provided', () => {
const out = getMediaQueries('test', {
test: {
minWidth: '1px',
},
})

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

it('should return an empty string if an invalid value is passed', () => {
const out = getMediaQueries('test2', {
test: {
invalidValue: 'meow',
},
})

expect(out).toEqual({})
})
})

describe('getSingleResolutionProps', () => {
it('should return the object as is if there are no resolution or the "show" props', () => {
const props = { a: 1, b: 2 }
const out = getSingleResolutionProps({
props,
resolutionKeys: [],
shouldShow: {
small: false,
large: false,
},
fallbackDisplayKey: 'default',
})

expect(out).toEqual(props)
})

it('should remove the "show" prop', () => {
const props = { a: 1, b: 2, show: 'small, large' }
const out = getSingleResolutionProps({
props,
resolutionKeys: [],
shouldShow: {
small: false,
large: false,
},
fallbackDisplayKey: 'default',
})

expect(out.show).not.toBeDefined()
})

it('should pick the resolution keys based on "shouldShow"', () => {
const props = { a: { small: 1, large: 2 }, b: { small: 3, large: 4 } }
const out = getSingleResolutionProps({
props,
resolutionKeys: ['a', 'b'],
shouldShow: {
small: true,
large: false,
},
fallbackDisplayKey: 'default',
})

expect(out).toEqual({
a: 1,
b: 3,
})
})

it('should not change keys if they are not specified as "resolutionKeys"', () => {
const props = {
a: { small: 1, large: 2 },
b: { small: 3, large: 4 },
c: { small: 5, large: 6 },
}
const out = getSingleResolutionProps({
props,
resolutionKeys: ['a', 'b'],
shouldShow: {
small: true,
large: false,
},
fallbackDisplayKey: 'default',
})

expect(out.c).toEqual(props.c)
})

it('should fall back to the default resolution key when none is provided', () => {
const props = {
a: { small: 1, large: 2 },
b: { small: 3, large: 4 },
c: { small: 5, large: 6 },
}
const out = getSingleResolutionProps({
props,
resolutionKeys: ['a', 'b'],
shouldShow: {
small: false,
large: false,
},
fallbackDisplayKey: 'large',
})

expect(out).toEqual({
a: 2,
b: 4,
c: {
small: 5,
large: 6,
},
})
})
})
44 changes: 0 additions & 44 deletions src/withResolution/withResolution.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react'
import { mount } from 'enzyme'
import log from '../utils/log'
import withResolution from './index'
import { getMediaQueries } from './withResolution.logic'

jest.mock('./mediaQuery')

Expand Down Expand Up @@ -128,46 +127,3 @@ describe('withResolution', () => {
}
})
})

describe('getMediaQueries', () => {
it('should return a max and min value base on the available aliases', () => {
const out = getMediaQueries('test', {
test: {
minWidth: '1px',
maxWidth: '2px',
},
})

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

it('should return a max value only when no min value is provided', () => {
const out = getMediaQueries('test', {
test: {
maxWidth: '2px',
},
})

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

it('should return a min value only when no max value is provided', () => {
const out = getMediaQueries('test', {
test: {
minWidth: '1px',
},
})

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

it('should return an empty string if an invalid value is passed', () => {
const out = getMediaQueries('test2', {
test: {
invalidValue: 'meow',
},
})

expect(out).toEqual({})
})
})
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.
22 changes: 22 additions & 0 deletions storybook/stories/configProvider/fallbackDisplayKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @flow
import * as React from 'react'
import { ConfigProvider, Grid } from 'gymnast'
import { colors } from '../../shared'

export default () => (
<ConfigProvider fallbackDisplayKey="fallback">
<Grid padding="0 L/2">
<Grid padding="0 L/2 L" size={{ large: 2, fallback: 10 }}>
<Grid
style={colors.colors1}
padding="L/2"
align="center"
justify="center"
>
Only size on large screens is specified, all others use the fallback
size
</Grid>
</Grid>
</Grid>
</ConfigProvider>
)
21 changes: 21 additions & 0 deletions storybook/stories/configProvider/fallbackDisplayKey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Some times it is inconvenient to specify all display keys when you only want to change one value on one resolution. `fallbackDisplayKey` allows you to provide a fallback value.

For instance, if you want to set size to 2 columns on large displays but to 10 on all other ones, you could do:

```javascript
<Grid size={{ large: 2, medium: 10, small: 10 }}>
```

This is fine when you have 2 or 3 breakpoints but it quickly becomes cumbersome. Instead you can also do:

```javascript
<Grid size={{ large: 2, default: 10 }}>
```

Note that `'default'` is the default value for the `fallbackDisplayKey` prop but you can modify it using `<ConfigProvider />`. For instance:

```javascript
<ConfigProvider fallbackDisplayKey="fallback">
<Grid size={{ large: 2, fallback: 10 }}>
</ConfigProvider>
```
Loading

0 comments on commit c3232d7

Please sign in to comment.