UNPKG

recastui

Version:

Solidjs components library focused on usability, whitelabel theming, accessibility and developer experience

569 lines (528 loc) 14.8 kB
import { pseudoSelectors } from './pseudo-selectors.js'; /*! * Original code by Theme-ui * MIT Licensed, Copyright (c) Brent Jackson. * * Credits to the Theme-ui team: * https://github.com/system-ui/theme-ui/blob/develop/packages/css/src/index.ts */ /** * Allows for nested scales with shorthand values * @example * { * colors: { * primary: { __default: '#00f', light: '#33f' } * } * } * css({ color: 'primary' }); // { color: '#00f' } * css({ color: 'primary.light' }) // { color: '#33f' } */ const RECAST_UI_DEFAULT_KEY = '__default'; const hasDefault = x => { return typeof x === 'object' && x !== null && RECAST_UI_DEFAULT_KEY in x; }; /** * Extracts value under path from a deeply nested object. * Used for Themes, variants and Theme UI style objects. * Given a path to object with `__default` key, returns the value under that key. * * @param obj a theme, variant or style object * @param path path separated with dots (`.`) * @param fallback default value returned if get(obj, path) is not found * @param p * @param undef */ function get(obj, path, fallback, p, undef) { const pathArray = path && typeof path === 'string' ? path.split('.') : [path]; for (p = 0; p < pathArray.length; p++) { obj = obj ? obj[pathArray[p]] : undef; } if (obj === undef) return fallback; return hasDefault(obj) ? obj[RECAST_UI_DEFAULT_KEY] : obj; } const getObjectWithVariants = (obj, theme) => { if (obj && obj['variant']) { let result = {}; for (const key in obj) { const x = obj[key]; if (key === 'variant') { const val = typeof x === 'function' ? x(theme) : x; const variant = getObjectWithVariants(get(theme, val), theme); result = { ...result, ...variant }; } else { result[key] = x; } } return result; } return obj; }; const defaultBreakpoints = ['576px', '768px', '992px', '1200px']; const baseColors = { white: '#ffffff', black: '#121212', gray: ['#ffffff', // 0 index '#f8f9fa', '#e9ecef', '#dee2e6', '#ced4da', '#adb5bd', '#6c757d', '#495057', '#343a40', '#212529'], blue: '#007bff', indigo: '#6610f2', purple: '#6f42c1', pink: '#e83e8c', red: '#dc3545', orange: '#fd7e14', yellow: '#ffc107', green: '#28a745', teal: '#20c997', cyan: '#17a2b8' }; const colors = { ...baseColors, grayDark: baseColors.gray[8], text: baseColors.gray[9], background: baseColors.white, primary: baseColors.blue, secondary: baseColors.gray[6], muted: baseColors.gray[3], success: baseColors.green, info: baseColors.cyan, warning: baseColors.yellow, danger: baseColors.red, light: baseColors.gray[1], dark: baseColors.gray[8], textMuted: baseColors.gray[6] }; const space = { '0.5': '0.125rem', '1': '0.25rem', '1.5': '0.375rem', '2': '0.5rem', '2.5': '0.625rem', '3': '0.75rem', '3.5': '0.875rem', '4': '1rem', '5': '1.25rem', '6': '1.5rem', '7': '1.75rem', '8': '2rem', '9': '2.25rem', '10': '2.5rem', '12': '3rem', '14': '3.5rem', '16': '4rem', '20': '5rem', '24': '6rem', '28': '7rem', '32': '8rem', '36': '9rem', '40': '10rem', '44': '11rem', '48': '12rem', '52': '13rem', '56': '14rem', '60': '15rem', '64': '16rem', '72': '18rem', '80': '20rem', '96': '24rem' }; const defaultTheme = { space, fonts: { sans: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", serif: "ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif", mono: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace" }, fontSizes: { xs: '0.75rem', sm: '0.875rem', base: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', '6xl': '3.75rem', '7xl': '4.5rem', '8xl': '6rem', '9xl': '8rem' }, fontWeights: { hairline: 100, thin: 200, light: 300, normal: 400, medium: 500, semibold: 600, bold: 700, extrabold: 800, black: 900 }, lineHeights: { none: 1, shorter: 1.25, short: 1.375, base: 1.5, tall: 1.625, taller: 2, '3': '0.75rem', '4': '1rem', '5': '1.25rem', '6': '1.5rem', '7': '1.75rem', '8': '2rem', '9': '2.25rem', '10': '2.5rem' }, letterSpacings: { tighter: '-0.05em', tight: '-0.025em', normal: '0', wide: '0.025em', wider: '0.05em', widest: '0.1em' }, sizes: { ...space, max: 'max-content', min: 'min-content', full: '100%', screenW: '100vw', screenH: '100vh', xs: '20rem', sm: '24rem', md: '28rem', lg: '32rem', xl: '36rem', '2xl': '42rem', '3xl': '48rem', '4xl': '56rem', '5xl': '64rem', '6xl': '72rem', '7xl': '80rem', '8xl': '90rem' }, radii: { none: '0', xs: '0.125rem', sm: '0.25rem', md: '0.375rem', lg: '0.5rem', xl: '0.75rem', '2xl': '1rem', '3xl': '1.5rem', full: '9999px' }, shadows: { none: '0 0 #0000', xs: '0px 1px 2px rgb(16 24 40 / 5%)', sm: '0px 1px 3px rgb(16 24 40 / 10%), 0px 1px 2px rgb(16 24 40 / 6%)', md: '0px 4px 8px -2px rgb(16 24 40 / 10%), 0px 2px 4px -2px rgb(16 24 40 / 6%)', lg: '0px 12px 16px -4px rgb(16 24 40 / 8%), 0px 4px 6px -2px rgb(16 24 40 / 3%)', xl: '0px 20px 24px -4px rgb(16 24 40 / 8%), 0px 8px 8px -4px rgb(16 24 40 / 3%)', '2xl': '0px 24px 48px -12px rgb(16 24 40 / 18%)', '3xl': '0px 32px 64px -12px rgb(16 24 40 / 14%)', inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.06)' }, zIndices: { hide: -1, base: 0, docked: 10, sticky: 1000, banner: 1100, overlay: 1200, modal: 1300, dropdown: 1400, popover: 1500, tooltip: 1600, skipLink: 1700, toast: 1800 } }; const aliases = { bg: 'backgroundColor', bgColor: 'backgroundColor', m: 'margin', mt: 'marginTop', mr: 'marginRight', mb: 'marginBottom', ml: 'marginLeft', mx: 'marginX', my: 'marginY', me: 'marginEnd', p: 'padding', pt: 'paddingTop', pr: 'paddingRight', pb: 'paddingBottom', pl: 'paddingLeft', px: 'paddingX', py: 'paddingY', pe: 'paddingEnd', d: 'display', w: 'width', minW: 'minWidth', maxW: 'maxWidth', h: 'height', minH: 'minHeight', maxH: 'maxHeight', boxSize: 'boxSizing', pos: 'position' }; const multiples = { marginX: ['marginLeft', 'marginRight'], marginY: ['marginTop', 'marginBottom'], paddingX: ['paddingLeft', 'paddingRight'], paddingY: ['paddingTop', 'paddingBottom'], scrollMarginX: ['scrollMarginLeft', 'scrollMarginRight'], scrollMarginY: ['scrollMarginTop', 'scrollMarginBottom'], scrollPaddingX: ['scrollPaddingLeft', 'scrollPaddingRight'], scrollPaddingY: ['scrollPaddingTop', 'scrollPaddingBottom'], size: ['width', 'height'] }; const scales = { color: 'colors', background: 'colors', backgroundColor: 'colors', borderColor: 'colors', caretColor: 'colors', columnRuleColor: 'colors', outlineColor: 'colors', textDecorationColor: 'colors', opacity: 'opacities', transition: 'transitions', margin: 'space', marginTop: 'space', marginRight: 'space', marginBottom: 'space', marginLeft: 'space', marginX: 'space', marginY: 'space', marginBlock: 'space', marginBlockEnd: 'space', marginBlockStart: 'space', marginInline: 'space', marginInlineEnd: 'space', marginInlineStart: 'space', padding: 'space', paddingTop: 'space', paddingRight: 'space', paddingBottom: 'space', paddingLeft: 'space', paddingX: 'space', paddingY: 'space', paddingBlock: 'space', paddingBlockEnd: 'space', paddingBlockStart: 'space', paddingInline: 'space', paddingInlineEnd: 'space', paddingInlineStart: 'space', scrollMargin: 'space', scrollMarginTop: 'space', scrollMarginRight: 'space', scrollMarginBottom: 'space', scrollMarginLeft: 'space', scrollMarginX: 'space', scrollMarginY: 'space', scrollPadding: 'space', scrollPaddingTop: 'space', scrollPaddingRight: 'space', scrollPaddingBottom: 'space', scrollPaddingLeft: 'space', scrollPaddingX: 'space', scrollPaddingY: 'space', inset: 'space', insetBlock: 'space', insetBlockEnd: 'space', insetBlockStart: 'space', insetInline: 'space', insetInlineEnd: 'space', insetInlineStart: 'space', top: 'space', right: 'space', bottom: 'space', left: 'space', gridGap: 'space', gridColumnGap: 'space', gridRowGap: 'space', gap: 'space', columnGap: 'space', rowGap: 'space', fontFamily: 'fonts', fontSize: 'fontSizes', fontWeight: 'fontWeights', lineHeight: 'lineHeights', letterSpacing: 'letterSpacings', border: 'borders', borderTop: 'borders', borderRight: 'borders', borderBottom: 'borders', borderLeft: 'borders', borderWidth: 'borderWidths', borderStyle: 'borderStyles', borderRadius: 'radii', borderTopRightRadius: 'radii', borderTopLeftRadius: 'radii', borderBottomRightRadius: 'radii', borderBottomLeftRadius: 'radii', borderTopWidth: 'borderWidths', borderTopColor: 'colors', borderTopStyle: 'borderStyles', borderBottomWidth: 'borderWidths', borderBottomColor: 'colors', borderBottomStyle: 'borderStyles', borderLeftWidth: 'borderWidths', borderLeftColor: 'colors', borderLeftStyle: 'borderStyles', borderRightWidth: 'borderWidths', borderRightColor: 'colors', borderRightStyle: 'borderStyles', borderBlock: 'borders', borderBlockColor: 'colors', borderBlockEnd: 'borders', borderBlockEndColor: 'colors', borderBlockEndStyle: 'borderStyles', borderBlockEndWidth: 'borderWidths', borderBlockStart: 'borders', borderBlockStartColor: 'colors', borderBlockStartStyle: 'borderStyles', borderBlockStartWidth: 'borderWidths', borderBlockStyle: 'borderStyles', borderBlockWidth: 'borderWidths', borderEndEndRadius: 'radii', borderEndStartRadius: 'radii', borderInline: 'borders', borderInlineColor: 'colors', borderInlineEnd: 'borders', borderInlineEndColor: 'colors', borderInlineEndStyle: 'borderStyles', borderInlineEndWidth: 'borderWidths', borderInlineStart: 'borders', borderInlineStartColor: 'colors', borderInlineStartStyle: 'borderStyles', borderInlineStartWidth: 'borderWidths', borderInlineStyle: 'borderStyles', borderInlineWidth: 'borderWidths', borderStartEndRadius: 'radii', borderStartStartRadius: 'radii', columnRuleWidth: 'borderWidths', boxShadow: 'shadows', textShadow: 'shadows', zIndex: 'zIndices', width: 'sizes', minWidth: 'sizes', maxWidth: 'sizes', height: 'sizes', minHeight: 'sizes', maxHeight: 'sizes', flexBasis: 'sizes', size: 'sizes', blockSize: 'sizes', inlineSize: 'sizes', maxBlockSize: 'sizes', maxInlineSize: 'sizes', minBlockSize: 'sizes', minInlineSize: 'sizes', columnWidth: 'sizes', // svg fill: 'colors', stroke: 'colors' }; const positiveOrNegative = (scale, value) => { if (typeof value !== 'number' || value >= 0) { if (typeof value === 'string' && value.startsWith('-')) { const valueWithoutMinus = value.substring(1); const n = get(scale, valueWithoutMinus, valueWithoutMinus); if (typeof n === 'number') { return n * -1; } return `-${n}`; } return get(scale, value, value); } const absolute = Math.abs(value); const n = get(scale, absolute, absolute); if (typeof n === 'string') return '-' + n; return Number(n) * -1; }; const transforms = ['margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'marginX', 'marginY', 'marginBlock', 'marginBlockEnd', 'marginBlockStart', 'marginInline', 'marginInlineEnd', 'marginInlineStart', 'top', 'bottom', 'left', 'right'].reduce((acc, curr) => ({ ...acc, [curr]: positiveOrNegative }), {}); const responsive = styles => theme => { const next = {}; const breakpoints = theme && theme.breakpoints || defaultBreakpoints; const mediaQueries = [null, ...breakpoints.map(n => n.includes('@media') ? n : `@media screen and (min-width: ${n})`)]; for (const k in styles) { const key = k; let value = styles[key]; if (typeof value === 'function') { value = value(theme || {}); } if (value === false || value == null) { continue; } if (!Array.isArray(value)) { next[key] = value; continue; } for (let i = 0; i < value.slice(0, mediaQueries.length).length; i++) { const media = mediaQueries[i]; if (!media) { next[key] = value[i]; continue; } next[media] = next[media] || {}; if (value[i] == null) continue; next[media][key] = value[i]; } } return next; }; const systemCss = function (args) { if (args === void 0) { args = {}; } return function (props) { if (props === void 0) { props = {}; } const theme = { ...defaultTheme, ...('theme' in props ? props.theme : props) }; // insert variant props before responsive styles, so they can be merged // we need to maintain order of the style props, so if a variant is place in the middle // of other props, it will extend its props at that same location order. const obj = getObjectWithVariants(typeof args === 'function' ? args(theme) : args, theme); const styles = responsive(obj)(theme); const result = {}; for (const key in styles) { const x = styles[key]; const val = typeof x === 'function' ? x(theme) : x; if (val && typeof val === 'object') { if (hasDefault(val)) { result[key] = val[RECAST_UI_DEFAULT_KEY]; continue; } if (Object.keys(pseudoSelectors).includes(key)) { result[pseudoSelectors[key]] = systemCss(val)(theme); continue; } // On type level, val can also be an array here, // but we transform all arrays in `responsive` function. result[key] = systemCss(val)(theme); continue; } const prop = key in aliases ? aliases[key] : key; const scaleName = prop in scales ? scales[prop] : undefined; const scale = scaleName ? theme == null ? void 0 : theme[scaleName] : get(theme, prop, {}); const transform = get(transforms, prop, get); const value = transform(scale, val, val); if (prop in multiples) { const dirs = multiples[prop]; for (let i = 0; i < dirs.length; i++) { result[dirs[i]] = value; } } else { result[prop] = value; } } return result; }; }; export { RECAST_UI_DEFAULT_KEY, baseColors, colors, defaultBreakpoints, get, getObjectWithVariants, multiples, scales, space, systemCss };