Skip to content

Commit

Permalink
feat(react): add createTheme for theme customization
Browse files Browse the repository at this point in the history
  • Loading branch information
cheton committed Nov 23, 2024
1 parent 848cc51 commit d12ce0d
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 174 deletions.
41 changes: 19 additions & 22 deletions packages/react-docs/pages/_app.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ToastManager,
TonicProvider,
colorStyle as defaultColorStyle,
createTheme,
theme,
useTheme,
} from '@tonic-ui/react';
Expand Down Expand Up @@ -50,28 +51,24 @@ const EmotionCacheProvider = ({
};

const App = (props) => {
const customTheme = useConst(() => {
return {
...theme,
components: {
// Set default props for components here.
//
// Example:
// ```
// 'AccordionToggle': {
// defaultProps: {
// disabled: true,
// },
// }
// ```
},
config: {
...theme?.config,
// Enable CSS variables replacement
useCSSVariables: true,
},
};
});
const customTheme = useConst(() => createTheme(theme, {
components: {
// Set default props for specific components
//
// Example:
// ```
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// },
// ```
},
config: {
// Enable CSS variables replacement
useCSSVariables: true,
},
}));
const [initialColorMode, setColorMode] = useState(null);
const router = useRouter();

Expand Down
73 changes: 33 additions & 40 deletions packages/react-docs/pages/getting-started/usage/index.page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,30 @@ import {
Box,
TonicProvider,
colorStyle, // [optional] Required only for customizing color styles
createTheme,
theme, // [optional] Required only for customizing the theme
} from '@tonic-ui/react';
import { useConst } from '@tonic-ui/react-hooks';

function App(props) {
const customTheme = {
...theme,
const customTheme = useConst(() => createTheme(theme, {
components: {
// Set default props for specific components
//
// Example:
// ```js
// components: {
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// ```
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// }
// },
// ```
components: {},
// Enable CSS variables
config: {
...theme?.config,
useCSSVariables: true,
},
};
};
},
config: {
// Enable CSS variables replacement
useCSSVariables: true,
},
}));

return (
<TonicProvider
Expand Down Expand Up @@ -69,34 +67,32 @@ import {
ToastManager, // allows you to create and manage toasts in the application
TonicProvider,
colorStyle,
createTheme,
theme,
useColorMode,
useTheme,
} from '@tonic-ui/react';
import { useConst } from '@tonic-ui/react-hooks';

function App(props) {
const customTheme = {
...theme,
const customTheme = useConst(() => createTheme(theme, {
components: {
// Set default props for specific components
//
// Example:
// ```js
// components: {
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// ```
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// }
// },
// ```
components: {},
// Enable CSS variables
config: {
...theme?.config,
useCSSVariables: true,
},
};
};
},
config: {
// Enable CSS variables replacement
useCSSVariables: true,
},
}));

return (
<TonicProvider
Expand Down Expand Up @@ -336,24 +332,21 @@ Optionally, you can extend the theme object to override the defaults with custom
Override the theme object and pass it to the `<ThemeProvider>` component.
```js
import { theme } from '@tonic-ui/react';
import { createTheme, theme } from '@tonic-ui/react';

// Let's say you want to add more colors
const customTheme = {
...theme,
const customTheme = createTheme(theme, {
config: {
...theme?.config,
useCSSVariables: true,
},
colors: {
...theme?.colors,
brand: {
90: "#1a365d",
80: "#153e75",
70: "#2a69ac",
},
},
};
});
```
#### 2. Setting up the provider
Expand Down
7 changes: 3 additions & 4 deletions packages/react-docs/pages/theme/breakpoints/index.page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ To do this, add a `breakpoints` array with additional aliases (e.g. `sm`, `md`,

```jsx disabled
// 1. Import the theme
import { ThemeProvider, theme } from '@tonic-ui/react';
import { ThemeProvider, createTheme, theme } from '@tonic-ui/react';

// 2. Update the breakpoints
const breakpoints = [
Expand All @@ -59,10 +59,9 @@ breakpoints.xl = breakpoints[3];
breakpoints['2xl'] = breakpoints[4];

// 3. Extend the theme
const customTheme = {
...theme,
const customTheme = createTheme(theme, {
breakpoints,
};
});

// 4. Pass the custom theme to the theme provider
function App() {
Expand Down
45 changes: 20 additions & 25 deletions packages/react-docs/sandbox/create-react-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ import {
ToastManager,
TonicProvider,
colorStyle,
createTheme,
theme,
useColorMode,
useColorStyle,
useTheme,
} from '@tonic-ui/react';
import { useConst } from '@tonic-ui/react-hooks';
import { merge } from '@tonic-ui/utils';
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';
const customColorStyle = {
...colorStyle,
const customColorStyle = merge(colorStyle, {
dark: {
...colorStyle.dark,
// Add custom colors here
risk: {
high: 'red:50',
Expand All @@ -58,8 +58,6 @@ const customColorStyle = {
},
},
light: {
...colorStyle.light,
// Add custom colors here
risk: {
high: 'red:60',
Expand All @@ -75,30 +73,27 @@ const customColorStyle = {
info: 'gray:50',
},
},
};
});
const Root = (props) => {
const customTheme = {
...theme,
// Set default props for specific components
//
// Example:
// \`\`\`js
// components: {
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// },
// }
// \`\`\`
components: {},
// Enable CSS variables
const customTheme = useConst(() => createTheme(theme, {
components: {
// Set default props for specific components
//
// Example:
// \`\`\`
// 'ToastCloseButton': {
// defaultProps: {
// 'aria-label': 'Close toast',
// },
// },
// \`\`\`
},
config: {
...theme?.config,
// Enable CSS variables replacement
useCSSVariables: true,
},
};
}));
return (
<TonicProvider
Expand Down
1 change: 1 addition & 0 deletions packages/react/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ test('should match expected exports', () => {

// theme
'ThemeProvider',
'createTheme',
'theme',
'useTheme',

Expand Down
7 changes: 2 additions & 5 deletions packages/react/src/provider/TonicProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ import { CSSBaseline } from '../css-baseline';

const TonicProvider = ({
children,
colorMode: colorModeProps,
colorStyle: colorStyleProps,
colorMode: colorModeProps = {},
colorStyle: colorStyleProps = {},
theme,
useCSSBaseline = false,
}) => {
colorModeProps = colorModeProps ?? {};
colorStyleProps = colorStyleProps ?? {};

if (typeof colorModeProps !== 'object') {
console.error(
'TonicProvider: "colorMode" prop must be an object if provided.\n' +
Expand Down
69 changes: 3 additions & 66 deletions packages/react/src/theme/ThemeProvider.js
Original file line number Diff line number Diff line change
@@ -1,83 +1,20 @@
import {
ThemeProvider as StyledEngineThemeProvider,
} from '@emotion/react';
import originalTheme from '@tonic-ui/theme';
import { ensurePlainObject, ensureString } from 'ensure-type';
import React, { useMemo } from 'react';
import React from 'react';
import { DefaultPropsProvider } from '../default-props';
import CSSVariables from './CSSVariables';
import defaultTheme from './theme';
import flatten from './utils/flatten';
import toCSSVariable from './utils/toCSSVariable';

const originalThemeScales = Object.keys(originalTheme);

/**
* Generate CSS variable map for a given theme object.
*
* @param {object} theme - The object containing the theme values.
* @param {object} [options] - The options object.
* @param {string} [options.prefix] - A prefix to prepend to each generated CSS variable.
*
* @example
* ```js
* const theme = {
* colors: {
* 'blue:50': '#578aef',
* },
* };
* createCSSVariableMap(theme, { prefix: 'tonic' });
* // => {
* // '--tonic-colors-blue-50': '#578aef'
* // }
* ```
*/
const createCSSVariableMap = (theme, options) => {
const prefix = ensureString(options?.prefix);
const tokens = flatten(theme);
const cssVariableMap = {};

for (const [name, value] of Object.entries(tokens)) {
// name='colors.blue:50', prefix='tonic'
// => '--tonic-colors-blue-50'
const variable = toCSSVariable(name, { prefix });
cssVariableMap[variable] = value;
}

return cssVariableMap;
};

const ThemeProvider = ({
children,
theme: themeProp,
}) => {
const theme = themeProp ?? defaultTheme;
const computedTheme = useMemo(() => {
const themeConfig = {
...defaultTheme.config,
...theme?.config,
};

// Filter only the theme scales that are supported by the original theme
const normalizedTheme = Object.fromEntries(
Object.entries(ensurePlainObject(theme)).filter(
([key]) => originalThemeScales.includes(key)
)
);

// Create CSS variable map for the theme
const cssVariableMap = createCSSVariableMap(normalizedTheme, { prefix: themeConfig.prefix });

return {
...theme,
config: themeConfig,
__cssVariableMap: cssVariableMap,
};
}, [theme]);

return (
<StyledEngineThemeProvider theme={computedTheme}>
<DefaultPropsProvider value={computedTheme.components}>
<StyledEngineThemeProvider theme={theme}>
<DefaultPropsProvider value={theme?.components}>
<CSSVariables />
{children}
</DefaultPropsProvider>
Expand Down
Loading

0 comments on commit d12ce0d

Please sign in to comment.