UNPKG

@devseed-ui/theme-provider

Version:
125 lines (112 loc) 3.94 kB
import { css } from 'styled-components'; import get from 'lodash.get'; const availableRanges = ['xsmall', 'small', 'medium', 'large', 'xlarge']; const availableBounds = ['Up', 'Only', 'Down']; /** * Create the 'Up', 'Only', and 'Down' options for the given media label. * Logs a warning to the console when the use media query makes no sense, * switching to the best alternative possible. * * @param {string} label Media query range label */ /** * Create the css media query expression, validating the input range and the * desired media query bound type (Up, Down, Only) * Logs a warning to the console when the use media query makes no sense, * switching to the best alternative possible. * * @param {array} range The media query range [upper, lower] bounds. * @param {string} label The media query range label * @param {string} type The media query bound type. One of (Up, Down, Only). */ function buildMediaExp(range, label, type) { const getExpr = ([lower, upper], type) => { switch (type) { case 'Up': // It makes no sense to have a media query "Up" from nothing. // There's no need for a media query in that case. if (lower === null) return null; return `only screen and (min-width: ${lower}px)`; case 'Only': // This only makes sense when there are both bounds. // Otherwise the "Up" or "Down" can be used with the same result. if (lower === null || upper === null) return null; return `only screen and (min-width: ${lower}px and max-width: ${upper}px)`; case 'Down': // It makes no sense to have a media query "Down" from infinity. // There's no need for a media query in that case. if (upper === null) return null; return `only screen and (max-width: ${upper}px)`; } }; const missingRange = range[0] === null ? 'lower' : range[1] === null ? 'upper' : null; const expr = getExpr(range, type); if (expr === null && (type === 'Up' || type === 'Down')) { // eslint-disable-next-line console.warn(`Media query warning: The specified media query (${label}${type}) has no ${missingRange} bound. There's no need for a media query in this case;`); return null; } else if (expr === null) { // eslint-disable-next-line console.warn(`Media query warning: The specified media query (${label}${type}) has no ${missingRange} bound. You can use (${label}${range[0] === null ? 'Down' : 'Up'}) instead.`); return getExpr(range, range[0] === null ? 'Down' : 'Up'); } return expr; } /** * Create the 'Up', 'Only', and 'Down' options for the given media label. * * @param {string} label Media query range label */ function createMediaRangeBounds(label) { return availableBounds.reduce( (acc, type) => ({ ...acc, [`${label}${type}`]: (...args) => { return (props) => { const range = get(props.theme, ['mediaRanges', label]); if (!range) { console.warn(`Media query warning: Missing bounds for (${label}) range. Add them to the theme under (mediaRanges: { ${label}: [upper, lower] })`); } const expr = range ? buildMediaExp(range, label, type) : null; return expr ? css` @media ${expr} { ${css(...args)} } ` : css(...args); }; } }), {} ); } /** * Build the media query utility where for each range there is the 'Up', 'Only', * and 'Down' options. * * Available media queries: * xsmallDown * smallUp, smallOnly, smallDown * mediumUp, mediumOnly, mediumDown * largeUp, largeOnly, largeDown * xlargeUp * * To use with style components as: * * @example * media.smallOnly` * color: red; * ` */ const media = availableRanges.reduce((acc, label) => { return { ...acc, ...createMediaRangeBounds(label) }; }, {}); export default media;