diff --git a/src/configProvider/index.js b/src/configProvider/index.js index f0b1964a9..cbab521fd 100644 --- a/src/configProvider/index.js +++ b/src/configProvider/index.js @@ -13,6 +13,7 @@ type Props = { children?: React.Node, columns?: number, displayAliases?: DisplayAliases, + fallbackDisplayKey?: string, gutter?: number, maxPageWidth?: number, minPageWidth?: number, @@ -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, diff --git a/src/defaults.js b/src/defaults.js index d1ef0cff0..db899c693 100644 --- a/src/defaults.js +++ b/src/defaults.js @@ -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, @@ -36,5 +43,5 @@ export default { '2XL/2': 3, '2XL': 6, }, - verticalGutter: 3, + verticalGutter: 3, // value (in base units) that separates columns vertically } diff --git a/src/types.js b/src/types.js index abbfa4b8c..2acb2b621 100644 --- a/src/types.js +++ b/src/types.js @@ -59,6 +59,7 @@ export type ConfigProviderContext = { +base?: number, +columns?: number, +displayAliases?: DisplayAliases, + +fallbackDisplayKey?: string, +gutter?: number, +maxPageWidth?: number, +minPageWidth?: number, diff --git a/src/withResolution/index.js b/src/withResolution/index.js index 8bf78c949..c276b85b8 100644 --- a/src/withResolution/index.js +++ b/src/withResolution/index.js @@ -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 } diff --git a/src/withResolution/withResolution.logic.js b/src/withResolution/withResolution.logic.js index 629234aa2..3682f6721 100644 --- a/src/withResolution/withResolution.logic.js +++ b/src/withResolution/withResolution.logic.js @@ -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 } @@ -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) { @@ -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 = [] -) { +export function getSingleResolutionProps({ + props, + shouldShow, + resolutionKeys = [], + fallbackDisplayKey = defaults.fallbackDisplayKey, +}: {| + +props: { show?: DisplayValues }, + +shouldShow?: ShouldShow, + +resolutionKeys: Array, + +fallbackDisplayKey: string, +|}) { const { ...propsCopy } = props delete propsCopy.show @@ -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) } }) diff --git a/src/withResolution/withResolution.logic.spec.js b/src/withResolution/withResolution.logic.spec.js new file mode 100644 index 000000000..048dd7d7d --- /dev/null +++ b/src/withResolution/withResolution.logic.spec.js @@ -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, + }, + }) + }) +}) diff --git a/src/withResolution/withResolution.spec.js b/src/withResolution/withResolution.spec.js index 090aa1da3..d79fc0aca 100644 --- a/src/withResolution/withResolution.spec.js +++ b/src/withResolution/withResolution.spec.js @@ -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') @@ -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({}) - }) -}) diff --git a/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey.spec.png b/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey.spec.png new file mode 100644 index 000000000..95af1eadd Binary files /dev/null and b/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey.spec.png differ diff --git a/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey_mobile.spec.png b/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey_mobile.spec.png new file mode 100644 index 000000000..40917c1e3 Binary files /dev/null and b/storybook/stories/configProvider/__screenshots__/fallbackDisplayKey_mobile.spec.png differ diff --git a/storybook/stories/configProvider/fallbackDisplayKey.js b/storybook/stories/configProvider/fallbackDisplayKey.js new file mode 100644 index 000000000..6840ae907 --- /dev/null +++ b/storybook/stories/configProvider/fallbackDisplayKey.js @@ -0,0 +1,22 @@ +// @flow +import * as React from 'react' +import { ConfigProvider, Grid } from 'gymnast' +import { colors } from '../../shared' + +export default () => ( + + + + + Only size on large screens is specified, all others use the fallback + size + + + + +) diff --git a/storybook/stories/configProvider/fallbackDisplayKey.md b/storybook/stories/configProvider/fallbackDisplayKey.md new file mode 100644 index 000000000..12f27ddec --- /dev/null +++ b/storybook/stories/configProvider/fallbackDisplayKey.md @@ -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 + +``` + +This is fine when you have 2 or 3 breakpoints but it quickly becomes cumbersome. Instead you can also do: + +```javascript + +``` + +Note that `'default'` is the default value for the `fallbackDisplayKey` prop but you can modify it using ``. For instance: + +```javascript + + + +``` diff --git a/test/__snapshots__/storyshots.spec.js.snap b/test/__snapshots__/storyshots.spec.js.snap index 06749841b..93d29d718 100644 --- a/test/__snapshots__/storyshots.spec.js.snap +++ b/test/__snapshots__/storyshots.spec.js.snap @@ -9069,6 +9069,102 @@ exports[`Storyshots Config Provider Columns 1`] = ` `; +exports[`Storyshots Config Provider Fallback Display Key 1`] = ` + + + + + + Only size on large screens is specified, all others use the fallback size + + + + + +`; + exports[`Storyshots Config Provider Gutter 1`] = `