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