Skip to content

Commit

Permalink
feat: enhance support for the nested theme token structure (#958)
Browse files Browse the repository at this point in the history
* feat: enhance support for the nested theme token structure

* test: add tests for the `getter` transform

* feat(styled-system): enhance folder structure for better readability

* Create tonic-ui-956.md

* chore: update yarn.lock file

* feat: import `isNullish` and `merge` from `@tonic-ui/utils`

* chore: update yarn.lock file

* chore: update tonic-ui-956.md
  • Loading branch information
cheton authored Dec 18, 2024
1 parent 46dcfc1 commit 32c71ec
Show file tree
Hide file tree
Showing 25 changed files with 389 additions and 231 deletions.
5 changes: 5 additions & 0 deletions .changeset/tonic-ui-956.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tonic-ui/styled-system": minor
---

feat: enhance support for the nested theme token structure
4 changes: 4 additions & 0 deletions packages/styled-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/background.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/border.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import system from '../core/system';
import { positiveOrNegative as positiveOrNegativeTransform } from '../utils/transforms';
import positiveOrNegativeTransform from '../transforms/positiveOrNegative';

const _border = {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/margin.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
4 changes: 1 addition & 3 deletions packages/styled-system/src/config/outline.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/position.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/scroll.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/shape.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/styled-system/src/config/text.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
107 changes: 61 additions & 46 deletions packages/styled-system/src/core/parser.js
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -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)
);
Expand Down Expand Up @@ -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;
7 changes: 4 additions & 3 deletions packages/styled-system/src/core/system.js
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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 {};
}

Expand Down
9 changes: 5 additions & 4 deletions packages/styled-system/src/sx.js
Original file line number Diff line number Diff line change
@@ -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 {};
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -96,7 +97,7 @@ const createResponsiveResolver = theme => styleProps => {
};

const sx = (valueOrFn) => (props = {}) => {
if (valueOrFn === null || valueOrFn === undefined) {
if (isNullish(valueOrFn)) {
return {};
}

Expand Down
65 changes: 65 additions & 0 deletions packages/styled-system/src/transforms/__tests__/getter.test.js
Original file line number Diff line number Diff line change
@@ -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)');
});
});
Original file line number Diff line number Diff line change
@@ -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');
});
});
Loading

0 comments on commit 32c71ec

Please sign in to comment.