Skip to content

Commit

Permalink
feat: extract theme.vars into theme.cssVariables and `theme.cssVa…
Browse files Browse the repository at this point in the history
…riablePrefix` for improved clarity
  • Loading branch information
cheton committed Nov 29, 2024
1 parent 782ad84 commit 03e7d11
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const App = () => {

return (
<Box fontFamily="mono">
{Object.entries(theme?.vars).map(([name, value]) => {
{Object.entries(theme?.cssVariables).map(([name, value]) => {
if (!name.startsWith('--')) {
return null;
}
Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/theme/CSSVariables.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Global } from '@emotion/react';
import { ensureArray } from 'ensure-type';
import { ensureArray, ensurePlainObject } from 'ensure-type';
import React, { useCallback } from 'react';

const CSSVariables = ({
root = [':root', ':host'],
}) => {
const styles = useCallback((theme) => {
const cssVariables = ensurePlainObject(theme?.cssVariables);
if (Object.keys(cssVariables) === 0) {
return {};

Check warning on line 11 in packages/react/src/theme/CSSVariables.js

View check run for this annotation

Codecov / codecov/patch

packages/react/src/theme/CSSVariables.js#L11

Added line #L11 was not covered by tests
}
const selector = ensureArray(root).join(',');
return {
[selector]: { ...theme?.vars },
[selector]: cssVariables,
};
}, [root]);

Expand Down
62 changes: 41 additions & 21 deletions packages/react/src/theme/__tests__/createTheme.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import createTheme from '../createTheme';

describe('createTheme', () => {
const defaultThemeScales = [
'borders',
'breakpoints',
'colors',
'fonts',
'fontSizes',
'fontWeights',
'letterSpacings',
'lineHeights',
'outlines',
'radii',
'shadows',
'sizes',
'space',
'zIndices',
];

it('should create a default theme', () => {
const theme = createTheme();
expect(theme).toHaveProperty('borders');
expect(theme).toHaveProperty('breakpoints');
expect(theme).toHaveProperty('colors');
expect(theme).toHaveProperty('fonts');
expect(theme).toHaveProperty('fontSizes');
expect(theme).toHaveProperty('fontWeights');
expect(theme).toHaveProperty('letterSpacings');
expect(theme).toHaveProperty('lineHeights');
expect(theme).toHaveProperty('outlines');
expect(theme).toHaveProperty('radii');
expect(theme).toHaveProperty('shadows');
expect(theme).toHaveProperty('sizes');
expect(theme).toHaveProperty('space');
expect(theme).toHaveProperty('zIndices');
defaultThemeScales.forEach(scale => expect(theme).toHaveProperty(scale));
});

it('should merge custom theme options', () => {
Expand All @@ -41,20 +45,36 @@ describe('createTheme', () => {

it('should not generate CSS variables with default configuration', () => {
const theme = createTheme();
expect(theme.vars).not.toBeDefined();
expect(theme.cssVariables).not.toBeDefined();
});

it('should generate CSS variables', () => {
const theme = createTheme({ cssVariables: true });
expect(theme.vars).toBeDefined();
expect(theme.vars.prefix).toBe('tonic');
expect(Object.keys(theme.vars).filter(x => x.startsWith('--'))[0]).toMatch(/^--tonic-/);
expect(theme.cssVariables).toBeDefined();
expect(theme.cssVariablePrefix).toBe('tonic');
const cssVariableKeys = Object.keys(theme.cssVariables).filter(x => x.startsWith('--'));
expect(cssVariableKeys.length).toBeGreaterThan(0);
expect(cssVariableKeys[0]).toMatch(/^--tonic-/);
});

it('should apply custom prefix to CSS variables', () => {
const theme = createTheme({ cssVariables: { prefix: 'custom' } });
expect(theme.vars).toBeDefined();
expect(theme.vars.prefix).toBe('custom');
expect(Object.keys(theme.vars).filter(x => x.startsWith('--'))[0]).toMatch(/^--custom-/);
expect(theme.cssVariables).toBeDefined();
expect(theme.cssVariablePrefix).toBe('custom');
const cssVariableKeys = Object.keys(theme.cssVariables).filter(x => x.startsWith('--'));
expect(cssVariableKeys.length).toBeGreaterThan(0);
expect(cssVariableKeys[0]).toMatch(/^--custom-/);
});

it('should allow an empty prefix for CSS variables', () => {
const theme = createTheme({ cssVariables: { prefix: '' } });
expect(theme.cssVariables).toBeDefined();
expect(theme.cssVariablePrefix).toBe('');
const cssVariableKeys = Object.keys(theme.cssVariables).filter(x => x.startsWith('--'));
expect(cssVariableKeys.length).toBeGreaterThan(0);
cssVariableKeys.forEach(key => {
const isValid = defaultThemeScales.some(scale => key.startsWith(`--${scale}`));
expect(isValid).toBe(true);
});
});
});
6 changes: 2 additions & 4 deletions packages/react/src/theme/createTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ const createTheme = (options = {}, ...args) => {

// Merge CSS variables into the theme
theme = merge(theme, {
vars: {
prefix: cssVariablePrefix,
...cssVariables,
},
cssVariablePrefix,
cssVariables,
});
}

Expand Down
4 changes: 2 additions & 2 deletions packages/styled-system/src/__tests__/sx.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ const defaultTheme = {
test('should pass', () => {

Check warning on line 280 in packages/styled-system/src/__tests__/sx.test.js

View workflow job for this annotation

GitHub Actions / build

Arrow function has too many lines (309). Maximum allowed is 200
const theme = {
...defaultTheme,
vars: {
prefix: 'tonic',
cssVariablePrefix: 'tonic',
cssVariables: {
'--tonic-borders-1': '.0625rem solid',
'--tonic-borders-2': '.125rem solid',
'--tonic-borders-3': '.1875rem solid',
Expand Down
4 changes: 2 additions & 2 deletions packages/styled-system/src/config/__tests__/margin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const defaultTheme = {

const defaultThemeWithCSSVariables = {
...defaultTheme,
vars: {
prefix: 'tonic',
cssVariablePrefix: 'tonic',
cssVariables: {
'--tonic-breakpoints-0': '40em',
'--tonic-breakpoints-1': '52em',
'--tonic-breakpoints-2': '64em',
Expand Down
4 changes: 2 additions & 2 deletions packages/styled-system/src/config/__tests__/position.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const defaultTheme = {

const defaultThemeWithCSSVariables = {
...defaultTheme,
vars: {
prefix: 'tonic',
cssVariablePrefix: 'tonic',
cssVariables: {
'--tonic-space-0': 0,
'--tonic-space-1': 4,
'--tonic-space-2': 8,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*
* @return {string} The CSS variable name.
*/
const toCSSVariable = (name, options) => {
export const toCSSVariable = (name, options) => {
const {
prefix = '',
delimiter = '-',
Expand All @@ -19,5 +19,3 @@ const toCSSVariable = (name, options) => {
.replace(/^-+|-+$/g, ''); // trim hyphens from beginning and end of string
return `--${variableName}`;
};

export default toCSSVariable;
14 changes: 6 additions & 8 deletions packages/styled-system/src/utils/transforms.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import get from './get';
import toCSSVariable from './toCSSVariable';
import { toCSSVariable } from './css-vars';

// Check if a value is a simple CSS variable
// e.g. var(--tonic-spacing-1)
Expand All @@ -11,11 +11,10 @@ const isSimpleCSSVariable = (value) => {
// Negate the value, handling CSS variables and numeric values
const toNegativeValue = (scale, absoluteValue, options) => {
const theme = options?.props?.theme;
const hasCSSVariables = !!theme?.vars; // Defaults to false
const n = getter(scale, absoluteValue, options);

// Handle CSS variables for negative values
if (hasCSSVariables && isSimpleCSSVariable(n)) {
if (!!theme?.cssVariables && isSimpleCSSVariable(n)) {
// https://stackoverflow.com/questions/49469344/using-negative-css-custom-properties
return `calc(0px - ${n})`;
}
Expand All @@ -30,15 +29,14 @@ const toNegativeValue = (scale, absoluteValue, options) => {

export const getter = (scale, value, options) => {
const theme = options?.props?.theme;
const hasCSSVariables = !!theme?.vars; // Defaults to false
const result = get(scale, value);

if (result !== undefined && hasCSSVariables) {
// TODO: `theme?.config?.prefix` and `theme?.__cssVariableMap` are deprecated and will be removed in the next major release
if (result !== undefined && !!theme?.cssVariables) {
// TODO: `theme.config.prefix` and `theme.__cssVariableMap` are deprecated and will be removed in the next major release
const cssVariablePrefixFallback = theme?.config?.prefix;
const cssVariablePrefix = (theme?.vars?.prefix) ?? cssVariablePrefixFallback;
const cssVariablePrefix = (theme?.cssVariablePrefix) ?? cssVariablePrefixFallback;
const cssVariablesFallback = theme?.__cssVariableMap;
const cssVariables = (theme?.vars) ?? cssVariablesFallback;
const cssVariables = (theme?.cssVariables) ?? cssVariablesFallback;
const contextScale = options?.context?.scale;
const cssVariable = toCSSVariable(
// | contextScale | value |
Expand Down

0 comments on commit 03e7d11

Please sign in to comment.