diff --git a/.changeset/tonic-ui-956.md b/.changeset/tonic-ui-956.md new file mode 100644 index 0000000000..58416b4632 --- /dev/null +++ b/.changeset/tonic-ui-956.md @@ -0,0 +1,5 @@ +--- +"@tonic-ui/styled-system": minor +--- + +feat: enhance support for the nested theme token structure diff --git a/packages/styled-system/package.json b/packages/styled-system/package.json index 1bfbd9a6f3..849c277a28 100644 --- a/packages/styled-system/package.json +++ b/packages/styled-system/package.json @@ -19,6 +19,10 @@ "prepublish": "yarn run build", "test": "jest --maxWorkers=2" }, + "dependencies": { + "@tonic-ui/utils": "^2.1.1", + "ensure-type": "^1.5.1" + }, "devDependencies": { "@babel/cli": "^7.0.0", "@babel/core": "^7.0.0", diff --git a/packages/styled-system/src/config/background.js b/packages/styled-system/src/config/background.js index 0a796576d2..a9162cb6f5 100644 --- a/packages/styled-system/src/config/background.js +++ b/packages/styled-system/src/config/background.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'background'; const config = { diff --git a/packages/styled-system/src/config/border.js b/packages/styled-system/src/config/border.js index 2bcea63080..69c79c9de5 100644 --- a/packages/styled-system/src/config/border.js +++ b/packages/styled-system/src/config/border.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const _border = { /** diff --git a/packages/styled-system/src/config/margin.js b/packages/styled-system/src/config/margin.js index b102f23295..f46a02a9a3 100644 --- a/packages/styled-system/src/config/margin.js +++ b/packages/styled-system/src/config/margin.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'margin'; const config = { diff --git a/packages/styled-system/src/config/outline.js b/packages/styled-system/src/config/outline.js index 1c7f35d6b9..78460be707 100644 --- a/packages/styled-system/src/config/outline.js +++ b/packages/styled-system/src/config/outline.js @@ -1,7 +1,5 @@ import system from '../core/system'; -import { - positiveOrNegative as positiveOrNegativeTransform, -} from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'outline'; const config = { diff --git a/packages/styled-system/src/config/position.js b/packages/styled-system/src/config/position.js index 15f3927c78..0dfa6b2ca5 100644 --- a/packages/styled-system/src/config/position.js +++ b/packages/styled-system/src/config/position.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'position'; const config = { diff --git a/packages/styled-system/src/config/scroll.js b/packages/styled-system/src/config/scroll.js index 7cf91ec61e..ba5df87c19 100644 --- a/packages/styled-system/src/config/scroll.js +++ b/packages/styled-system/src/config/scroll.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'scroll'; const config = { diff --git a/packages/styled-system/src/config/shape.js b/packages/styled-system/src/config/shape.js index 159648459b..6c9692fd87 100644 --- a/packages/styled-system/src/config/shape.js +++ b/packages/styled-system/src/config/shape.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; /** * https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Shapes diff --git a/packages/styled-system/src/config/text.js b/packages/styled-system/src/config/text.js index 6601139da8..e6bc7398ae 100644 --- a/packages/styled-system/src/config/text.js +++ b/packages/styled-system/src/config/text.js @@ -1,5 +1,5 @@ import system from '../core/system'; -import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms'; +import positiveOrNegativeTransform from '../transforms/positiveOrNegative'; const group = 'text'; const config = { diff --git a/packages/styled-system/src/core/parser.js b/packages/styled-system/src/core/parser.js index 88e3b96524..d95a67335e 100644 --- a/packages/styled-system/src/core/parser.js +++ b/packages/styled-system/src/core/parser.js @@ -1,10 +1,67 @@ +import { merge } from '@tonic-ui/utils'; import get from '../utils/get'; -import mergeObject from '../utils/merge-object'; -import sortObject from '../utils/sort-object'; const defaultBreakpoints = []; const createMediaQuery = n => `@media screen and (min-width: ${n})`; +const parseResponsiveStyle = (mediaQueries, sx, scale, raw, _props) => { + const styles = {}; + + raw.slice(0, mediaQueries.length).forEach((value, i) => { + const media = mediaQueries[i]; + const style = sx(scale, value, _props); + if (!media) { + Object.assign(styles, style); + } else { + Object.assign(styles, { + [media]: Object.assign({}, styles[media], style), + }); + } + }); + + return styles; +}; + +const parseResponsiveObject = (breakpoints, sx, scale, raw, _props) => { + const styles = {}; + + for (let key in raw) { + if (!Object.prototype.hasOwnProperty.call(raw, key)) { + continue; + } + + const breakpoint = breakpoints[key]; + const value = raw[key]; + const style = sx(scale, value, _props); + if (!breakpoint) { + Object.assign(styles, style); + } else { + const media = createMediaQuery(breakpoint); + Object.assign(styles, { + [media]: Object.assign({}, styles[media], style), + }); + } + } + + return styles; +}; + +// sort object-value responsive styles +const sortObject = obj => { + const next = {}; + + Object.keys(obj) + .sort((a, b) => a.localeCompare(b, undefined, { + numeric: true, + sensitivity: 'base', + })) + .forEach(key => { + next[key] = obj[key]; + }); + + return next; +}; + const parser = config => { const cache = {}; const parse = props => { @@ -29,14 +86,14 @@ const parser = config => { null, ...cache.breakpoints.map(createMediaQuery), ]; - styles = mergeObject( + styles = merge( styles, parseResponsiveStyle(cache.media, sx, scale, raw, props) ); continue; } if (raw !== null) { - styles = mergeObject( + styles = merge( styles, parseResponsiveObject(cache.breakpoints, sx, scale, raw, props) ); @@ -72,46 +129,4 @@ const parser = config => { return parse; }; -const parseResponsiveStyle = (mediaQueries, sx, scale, raw, _props) => { - const styles = {}; - - raw.slice(0, mediaQueries.length).forEach((value, i) => { - const media = mediaQueries[i]; - const style = sx(scale, value, _props); - if (!media) { - Object.assign(styles, style); - } else { - Object.assign(styles, { - [media]: Object.assign({}, styles[media], style), - }); - } - }); - - return styles; -}; - -const parseResponsiveObject = (breakpoints, sx, scale, raw, _props) => { - const styles = {}; - - for (let key in raw) { - if (!Object.prototype.hasOwnProperty.call(raw, key)) { - continue; - } - - const breakpoint = breakpoints[key]; - const value = raw[key]; - const style = sx(scale, value, _props); - if (!breakpoint) { - Object.assign(styles, style); - } else { - const media = createMediaQuery(breakpoint); - Object.assign(styles, { - [media]: Object.assign({}, styles[media], style), - }); - } - } - - return styles; -}; - export default parser; diff --git a/packages/styled-system/src/core/system.js b/packages/styled-system/src/core/system.js index a755a99804..7af81e1885 100644 --- a/packages/styled-system/src/core/system.js +++ b/packages/styled-system/src/core/system.js @@ -1,5 +1,6 @@ -import ensureArray from '../utils/ensure-array'; -import { getter as getterTransform } from '../utils/transforms'; +import { isNullish } from '@tonic-ui/utils'; +import { ensureArray } from 'ensure-type'; +import getterTransform from '../transforms/getter'; import parser from './parser'; const system = (config, options) => { @@ -65,7 +66,7 @@ const createStyleFunction = ({ const sx = (scale, value, props) => { const transformOptions = { context, props }; const transformedValue = transform(scale, value, transformOptions); - if (transformedValue === null || transformedValue === undefined) { + if (isNullish(transformedValue)) { return {}; } diff --git a/packages/styled-system/src/sx.js b/packages/styled-system/src/sx.js index de775d6e45..8bd43bf85b 100644 --- a/packages/styled-system/src/sx.js +++ b/packages/styled-system/src/sx.js @@ -1,10 +1,11 @@ +import { isNullish } from '@tonic-ui/utils'; +import { ensureArray } from 'ensure-type'; import system from './system'; -import ensureArray from './utils/ensure-array'; import get from './utils/get'; import { pseudoClassSelector, pseudoElementSelector } from './pseudo'; const createPseudoResolver = (theme) => (styleProps) => { - if (styleProps === null || styleProps === undefined) { + if (isNullish(styleProps)) { return {}; } @@ -69,7 +70,7 @@ const createResponsiveResolver = theme => styleProps => { } const value = typeof styleProps[key] === 'function' ? styleProps[key](theme) : styleProps[key]; - if (value === null || value === undefined) { + if (isNullish(value)) { continue; } @@ -96,7 +97,7 @@ const createResponsiveResolver = theme => styleProps => { }; const sx = (valueOrFn) => (props = {}) => { - if (valueOrFn === null || valueOrFn === undefined) { + if (isNullish(valueOrFn)) { return {}; } diff --git a/packages/styled-system/src/transforms/__tests__/getter.test.js b/packages/styled-system/src/transforms/__tests__/getter.test.js new file mode 100644 index 0000000000..645ce89ae4 --- /dev/null +++ b/packages/styled-system/src/transforms/__tests__/getter.test.js @@ -0,0 +1,65 @@ +import getter from '../getter'; + +describe('getter', () => { + const theme = { + colors: { + // flat theme tokens + 'white:primary': 'rgba(255, 255, 255, .92)', + 'white:secondary': 'rgba(255, 255, 255, .60)', + 'black:primary': 'rgba(0, 0, 0, .92)', + 'black:secondary': 'rgba(0, 0, 0, .65)', + + // nested theme tokens + white: { + primary: { + value: 'rgba(255, 255, 255, .92)', + }, + secondary: { + value: 'rgba(255, 255, 255, .60)', + }, + }, + black: { + primary: { + value: 'rgba(0, 0, 0, .92)', + }, + secondary: { + value: 'rgba(0, 0, 0, .65)', + }, + }, + }, + }; + + it('should resolve flat color tokens', () => { + expect(getter(theme.colors, 'white:primary')).toBe('rgba(255, 255, 255, .92)'); + expect(getter(theme.colors, 'white:secondary')).toBe('rgba(255, 255, 255, .60)'); + expect(getter(theme.colors, 'black:primary')).toBe('rgba(0, 0, 0, .92)'); + expect(getter(theme.colors, 'black:secondary')).toBe('rgba(0, 0, 0, .65)'); + }); + + it('should resolve nested color tokens with dot notation', () => { + expect(getter(theme.colors, 'white.primary')).toBe('rgba(255, 255, 255, .92)'); + expect(getter(theme.colors, 'white.secondary')).toBe('rgba(255, 255, 255, .60)'); + expect(getter(theme.colors, 'black.primary')).toBe('rgba(0, 0, 0, .92)'); + expect(getter(theme.colors, 'black.secondary')).toBe('rgba(0, 0, 0, .65)'); + }); + + it('should fallback to original value when token path does not exist', () => { + expect(getter(theme.colors, 'white')).toBe('white'); + }); + + it('should handle undefined theme values', () => { + expect(getter(undefined, 'white.undefined')).toBe('white.undefined'); + }); + + it('should handle nested objects without value property', () => { + const customTheme = { + colors: { + custom: { + primary: 'rgb(100, 100, 100)', + }, + }, + }; + const result = getter(customTheme.colors, 'custom.primary'); + expect(result).toBe('rgb(100, 100, 100)'); + }); +}); diff --git a/packages/styled-system/src/transforms/__tests__/positiveOrNegative.test.js b/packages/styled-system/src/transforms/__tests__/positiveOrNegative.test.js new file mode 100644 index 0000000000..33902def51 --- /dev/null +++ b/packages/styled-system/src/transforms/__tests__/positiveOrNegative.test.js @@ -0,0 +1,42 @@ +import positiveOrNegative from '../positiveOrNegative'; + +describe('positiveOrNegative', () => { + const theme = { + sizes: { + '1x': '.25rem', + '2x': '.5rem', + '3x': '.75rem', + '4x': '1rem', + }, + }; + + it('should handle positive values', () => { + expect(positiveOrNegative(theme.sizes, '1x')).toBe('.25rem'); + expect(positiveOrNegative(theme.sizes, '2x')).toBe('.5rem'); + expect(positiveOrNegative(theme.sizes, '3x')).toBe('.75rem'); + expect(positiveOrNegative(theme.sizes, '4x')).toBe('1rem'); + }); + + it('should handle negative values', () => { + expect(positiveOrNegative(theme.sizes, '-1x')).toBe('-.25rem'); + expect(positiveOrNegative(theme.sizes, '-2x')).toBe('-.5rem'); + expect(positiveOrNegative(theme.sizes, '-3x')).toBe('-.75rem'); + expect(positiveOrNegative(theme.sizes, '-4x')).toBe('-1rem'); + }); + + it('should return original value when theme.sizes value is not found', () => { + expect(positiveOrNegative(theme.sizes, '5x')).toBe('5x'); + expect(positiveOrNegative(theme.sizes, '-5x')).toBe('-5x'); + }); + + it('should handle undefined theme.sizes', () => { + expect(positiveOrNegative(undefined, '2x')).toBe('2x'); + expect(positiveOrNegative(undefined, '-2x')).toBe('-2x'); + }); + + it('should handle non-numeric strings', () => { + expect(positiveOrNegative(theme.sizes, 'auto')).toBe('auto'); + expect(positiveOrNegative(theme.sizes, '4px')).toBe('4px'); + expect(positiveOrNegative(theme.sizes, '-4px')).toBe('-4px'); + }); +}); diff --git a/packages/styled-system/src/transforms/getter.js b/packages/styled-system/src/transforms/getter.js new file mode 100644 index 0000000000..13a83c535c --- /dev/null +++ b/packages/styled-system/src/transforms/getter.js @@ -0,0 +1,93 @@ +import get from '../utils/get'; + +/** + * Returns a CSS variable name formatted from the given name and options. + * + * @param {string} name - The name of the variable. + * @param {object} [options] - The options object. + * @param {string} [options.prefix=''] - The prefix to use for the variable name. + * @param {string} [options.delimiter='-'] - The delimiter to use between the prefix and name. + * + * @return {string} The CSS variable name. +*/ +const toCSSVariable = (name, options) => { + const { + prefix = '', + delimiter = '-', + } = { ...options }; + const variableName = ([prefix, name].filter(Boolean).join(delimiter)) + .replace(/\s+/g, delimiter) // replace whitespace characters + .replace(/[^a-zA-Z0-9-_]/g, delimiter) // replace non-alphanumeric, non-hyphen, non-underscore characters + .replace(/^-+|-+$/g, ''); // trim hyphens from beginning and end of string + return `--${variableName}`; +}; + +const getter = (scale, value, options) => { + let result = get(scale, value); + + // Extract the `value` property if the result is an object. + // + // Example usage: + // ``` + // + // + // ``` + // + // The `colors` scale in the theme: + // ```js + // { + // colors: { + // white: { + // primary: { + // value: 'rgba(255, 255, 255, .92)', + // }, + // secondary: { + // value: 'rgba(255, 255, 255, .60)', + // }, + // }, + // black: { + // primary: { + // value: 'rgba(0, 0, 0, .92)', + // }, + // secondary: { + // value: 'rgba(0, 0, 0, .65)', + // }, + // }, + // }, + // } + // ``` + if (typeof result === 'object') { + result = result?.value; + } + + if (result === undefined) { + return value; // fallback to value if result is undefined + } + + const theme = options?.props?.theme; + // FIXME: `theme.config.prefix` and `theme.__cssVariableMap` are deprecated and will be removed in the next major release + const hasCSSVariables = !!(theme?.cssVariables ?? theme?.__cssVariableMap); + if (hasCSSVariables) { + const cssVariablePrefix = (theme?.cssVariablePrefix) ?? (theme?.config?.prefix); + const cssVariables = (theme?.cssVariables) ?? (theme?.__cssVariableMap); + const contextScale = options?.context?.scale; + const cssVariable = toCSSVariable( + // | contextScale | value | + // | ------------ | --------- | + // | colors | 'blue:50' | + // | space | 0 | + [contextScale, String(value ?? '')].filter(Boolean).join('.'), // => 'colors.blue:50' + { prefix: cssVariablePrefix, delimiter: '-' }, + ); // => '--tonic-colors-blue-50' + const cssVariableValue = cssVariables?.[cssVariable]; // => '#578aef' + if (cssVariableValue !== undefined) { + // => Replace '#578aef' with 'var(--tonic-colors-blue-50)' + return String(result ?? '').replaceAll(cssVariableValue, `var(${cssVariable})`); + } + // fallback to the original result + } + + return result; +}; + +export default getter; diff --git a/packages/styled-system/src/utils/transforms.js b/packages/styled-system/src/transforms/positiveOrNegative.js similarity index 52% rename from packages/styled-system/src/utils/transforms.js rename to packages/styled-system/src/transforms/positiveOrNegative.js index 544a54be8c..49c884bc52 100644 --- a/packages/styled-system/src/utils/transforms.js +++ b/packages/styled-system/src/transforms/positiveOrNegative.js @@ -1,5 +1,15 @@ -import get from './get'; -import { toCSSVariable } from './css-vars'; +import { isNullish } from '@tonic-ui/utils'; +import getter from './getter'; + +const hasOwnSafe = (obj, key) => { + if (isNullish(obj)) { + return false; + } + + return Object.hasOwn + ? Object.hasOwn(obj, key) + : Object.prototype.hasOwnProperty.call(obj, key); +}; // Check if a value is a simple CSS variable // e.g. var(--tonic-spacing-1) @@ -27,39 +37,7 @@ const toNegativeValue = (scale, absoluteValue, options) => { return `-${n}`; }; -export const getter = (scale, value, options) => { - const result = get(scale, value); - if (result === undefined) { - return value; // fallback to value if result is undefined - } - - const theme = options?.props?.theme; - // FIXME: `theme.config.prefix` and `theme.__cssVariableMap` are deprecated and will be removed in the next major release - const hasCSSVariables = !!(theme?.cssVariables ?? theme?.__cssVariableMap); - if (hasCSSVariables) { - const cssVariablePrefix = (theme?.cssVariablePrefix) ?? (theme?.config?.prefix); - const cssVariables = (theme?.cssVariables) ?? (theme?.__cssVariableMap); - const contextScale = options?.context?.scale; - const cssVariable = toCSSVariable( - // | contextScale | value | - // | ------------ | --------- | - // | colors | 'blue:50' | - // | space | 0 | - [contextScale, String(value ?? '')].filter(Boolean).join('.'), // => 'colors.blue:50' - { prefix: cssVariablePrefix, delimiter: '-' }, - ); // => '--tonic-colors-blue-50' - const cssVariableValue = cssVariables?.[cssVariable]; // => '#578aef' - if (cssVariableValue !== undefined) { - // => Replace '#578aef' with 'var(--tonic-colors-blue-50)' - return String(result ?? '').replaceAll(cssVariableValue, `var(${cssVariable})`); - } - // fallback to the original result - } - - return result; -}; - -export const positiveOrNegative = (scale, value, options) => { +const positiveOrNegative = (scale, value, options) => { /** * Scale object * @@ -87,8 +65,13 @@ export const positiveOrNegative = (scale, value, options) => { const absoluteValue = (value.startsWith('+') || value.startsWith('-')) ? value.slice(1) : value; const isNonNegative = !value.startsWith('-'); - // Return the result if the value is non-negative or if the scale object does not contain the absolute value - if (isNonNegative || !Object.prototype.hasOwnProperty.call(scale, absoluteValue)) { + // Return the result if the scale object does not contain the absolute value + if (!hasOwnSafe(scale, absoluteValue)) { + return getter(scale, value, options); + } + + // Return the result if the value is non-negative + if (isNonNegative) { return getter(scale, value, options); } @@ -122,8 +105,13 @@ export const positiveOrNegative = (scale, value, options) => { const absoluteValue = Math.abs(value); const isNonNegative = !(value < 0); - // Return the result if the value is non-negative or if the scale object does not contain the absolute value - if (isNonNegative || !Object.prototype.hasOwnProperty.call(scale, absoluteValue)) { + // Return the result if the scale object does not contain the absolute value + if (!hasOwnSafe(scale, absoluteValue)) { + return getter(scale, value, options); + } + + // Return the result if the value is non-negative + if (isNonNegative) { return getter(scale, value, options); } @@ -132,3 +120,5 @@ export const positiveOrNegative = (scale, value, options) => { return getter(scale, value, options); }; + +export default positiveOrNegative; diff --git a/packages/styled-system/src/utils/__tests__/get.test.js b/packages/styled-system/src/utils/__tests__/get.test.js index 610476c55a..a3c5b03080 100644 --- a/packages/styled-system/src/utils/__tests__/get.test.js +++ b/packages/styled-system/src/utils/__tests__/get.test.js @@ -1,38 +1,68 @@ import get from '../get'; -test('returns a deeply nested value', () => { - const a = get( - { - colors: { - blue: ['#0cf', '#0be', '#09d', '#07c'], - }, - }, - 'colors.blue.3' - ); - expect(a).toBe('#07c'); -}); +describe('get function tests', () => { + describe('Default Value Handling', () => { + const obj = { a: { b: { c: 42 } } }; + const defaultValue = 'default'; -test('supports fallback values', () => { - const a = get({}, 'hi', 'nope'); - expect(a).toBe('nope'); -}); + test('returns default value for missing paths', () => { + expect(get(obj, 'a.b.c.d', defaultValue)).toBe(defaultValue); + expect(get(obj, ['a', 'b', 'c', 'd'], defaultValue)).toBe(defaultValue); + expect(get(obj, 'a.b.x', defaultValue)).toBe(defaultValue); + }); -test('handles number values', () => { - const a = get([1, 2, 3], 0); - expect(a).toBe(1); -}); + test('returns default value for null or undefined input', () => { + expect(get(obj, null, defaultValue)).toBe(defaultValue); + expect(get(obj, undefined, defaultValue)).toBe(defaultValue); + expect(get(null, 'a.b.c', defaultValue)).toBe(defaultValue); + expect(get(undefined, 'a.b.c', defaultValue)).toBe(defaultValue); + }); -test('handles undefined values', () => { - const a = get({}, undefined); - expect(a).toBe(undefined); -}); + test('returns default value for empty or undefined paths', () => { + expect(get(obj, '', defaultValue)).toBe(defaultValue); + expect(get(obj, ['a', 'b', 'x'], defaultValue)).toBe(defaultValue); + expect(get({ a: { b: undefined } }, 'a.b', defaultValue)).toBe(defaultValue); + }); + }); -test('handles null values', () => { - const a = get({}, null); - expect(a).toBe(undefined); -}); + describe('Value Retrieval', () => { + test('returns a deeply nested value', () => { + const result = get( + { + colors: { + blue: ['#0cf', '#0be', '#09d', '#07c'], + }, + }, + 'colors.blue.3' + ); + expect(result).toBe('#07c'); + }); + + test('supports fallback values', () => { + const result = get({}, 'hi', 'nope'); + expect(result).toBe('nope'); + }); + + test('handles number indices in arrays', () => { + const result = get([1, 2, 3], 0); + expect(result).toBe(1); + }); + + test('returns 0 index items from arrays', () => { + const result = get(['a', 'b', 'c'], 0); + expect(result).toBe('a'); + }); + }); + + describe('Edge Case Handling', () => { + test('handles undefined values gracefully', () => { + const result = get({}, undefined); + expect(result).toBe(undefined); + }); -test('returns 0 index items', () => { - const a = get(['a', 'b', 'c'], 0); - expect(a).toBe('a'); + test('handles null values gracefully', () => { + const result = get({}, null); + expect(result).toBe(undefined); + }); + }); }); diff --git a/packages/styled-system/src/utils/__tests__/merge-object.test.js b/packages/styled-system/src/utils/__tests__/merge-object.test.js deleted file mode 100644 index d6bac4d011..0000000000 --- a/packages/styled-system/src/utils/__tests__/merge-object.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import mergeObject from '../merge-object'; - -test('deeply merges', () => { - const result = mergeObject( - { - hello: 'hi', - media: { - howdy: 'ho', - }, - }, - { - beep: 'boop', - media: { - bleep: 'bloop', - }, - } - ); - expect(result).toEqual({ - hello: 'hi', - beep: 'boop', - media: { - howdy: 'ho', - bleep: 'bloop', - }, - }); -}); diff --git a/packages/styled-system/src/utils/css-vars.js b/packages/styled-system/src/utils/css-vars.js deleted file mode 100644 index b7c0dfd441..0000000000 --- a/packages/styled-system/src/utils/css-vars.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Returns a CSS variable name formatted from the given name and options. - * - * @param {string} name - The name of the variable. - * @param {object} [options] - The options object. - * @param {string} [options.prefix=''] - The prefix to use for the variable name. - * @param {string} [options.delimiter='-'] - The delimiter to use between the prefix and name. - * - * @return {string} The CSS variable name. -*/ -export const toCSSVariable = (name, options) => { - const { - prefix = '', - delimiter = '-', - } = { ...options }; - const variableName = ([prefix, name].filter(Boolean).join(delimiter)) - .replace(/\s+/g, delimiter) // replace whitespace characters - .replace(/[^a-zA-Z0-9-_]/g, delimiter) // replace non-alphanumeric, non-hyphen, non-underscore characters - .replace(/^-+|-+$/g, ''); // trim hyphens from beginning and end of string - return `--${variableName}`; -}; diff --git a/packages/styled-system/src/utils/ensure-array.js b/packages/styled-system/src/utils/ensure-array.js deleted file mode 100644 index 4fd7c3b617..0000000000 --- a/packages/styled-system/src/utils/ensure-array.js +++ /dev/null @@ -1,9 +0,0 @@ -const ensureArray = (value, defaultValue = []) => { - if (value === undefined || value === null) { - return [].concat(defaultValue); - } - - return Array.isArray(value) ? value : [].concat(value); -}; - -export default ensureArray; diff --git a/packages/styled-system/src/utils/get.js b/packages/styled-system/src/utils/get.js index 8d006f8014..d19102f65c 100644 --- a/packages/styled-system/src/utils/get.js +++ b/packages/styled-system/src/utils/get.js @@ -1,10 +1,16 @@ // based on https://github.com/developit/dlv -const get = (obj, key, def, p, undef) => { - key = key && key.split ? key.split('.') : [key]; - for (p = 0; p < key.length; p++) { - obj = obj ? obj[key[p]] : undef; +const get = (obj, key, defaultValue, undef) => { + if (key && key.split) { + key = key.split('.'); + } else { + key = Array.isArray(key) ? key : [key]; } - return obj === undef ? def : obj; + + for (let i = 0; i < key.length; ++i) { + obj = obj ? obj[key[i]] : undef; + } + + return obj === undef ? defaultValue : obj; }; export default get; diff --git a/packages/styled-system/src/utils/merge-object.js b/packages/styled-system/src/utils/merge-object.js deleted file mode 100644 index 9653928819..0000000000 --- a/packages/styled-system/src/utils/merge-object.js +++ /dev/null @@ -1,21 +0,0 @@ -const mergeObject = (a, b) => { - const result = Object.assign({}, a, b); - - for (const key in a) { - if (!Object.prototype.hasOwnProperty.call(a, key)) { - continue; - } - - if (!a[key] || typeof b[key] !== 'object') { - continue; - } - - Object.assign(result, { - [key]: Object.assign(a[key], b[key]), - }); - } - - return result; -}; - -export default mergeObject; diff --git a/packages/styled-system/src/utils/sort-object.js b/packages/styled-system/src/utils/sort-object.js deleted file mode 100644 index c20b07cc29..0000000000 --- a/packages/styled-system/src/utils/sort-object.js +++ /dev/null @@ -1,17 +0,0 @@ -// sort object-value responsive styles -const sortObject = obj => { - const next = {}; - - Object.keys(obj) - .sort((a, b) => a.localeCompare(b, undefined, { - numeric: true, - sensitivity: 'base', - })) - .forEach(key => { - next[key] = obj[key]; - }); - - return next; -}; - -export default sortObject; diff --git a/yarn.lock b/yarn.lock index b41326ffe6..619389a38b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4755,9 +4755,11 @@ __metadata: "@codecov/rollup-plugin": ^1.5.1 "@rollup/plugin-babel": ^6.0.0 "@rollup/plugin-node-resolve": ^15.0.0 + "@tonic-ui/utils": ^2.1.1 "@trendmicro/babel-config": ^1.0.2 cross-env: ^7.0.3 del-cli: ^5.0.0 + ensure-type: ^1.5.1 eslint: ^8.25.0 eslint-config-trendmicro: ^3.0.0 eslint-plugin-import: latest