Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance support for the nested theme token structure #958

Merged
merged 8 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Check warning on line 30 in packages/styled-system/src/core/parser.js

View check run for this annotation

Codecov / codecov/patch

packages/styled-system/src/core/parser.js#L30

Added line #L30 was not covered by tests
}

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 @@
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 @@
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
Loading