@devseed-ui/theme-provider
Version:
devseed UI Kit Theme
125 lines (112 loc) • 3.94 kB
JavaScript
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;