From fbb0229e47bbcbc24fe91d6d8e94ad94f77b66ed Mon Sep 17 00:00:00 2001 From: cheton Date: Sun, 1 Dec 2024 22:04:56 +0800 Subject: [PATCH 01/13] docs: custom theme with CSS variable configuration and update InstantSearch state for future changes --- packages/react-docs/pages/_app.page.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/react-docs/pages/_app.page.js b/packages/react-docs/pages/_app.page.js index c9909bb8b9..252f564358 100644 --- a/packages/react-docs/pages/_app.page.js +++ b/packages/react-docs/pages/_app.page.js @@ -51,7 +51,10 @@ const EmotionCacheProvider = ({ const App = (props) => { const customTheme = useConst(() => createTheme({ - cssVariables: true, // Enable CSS variables replacement + cssVariables: { + rootSelector: ':root', + prefix: 'tonic', + }, components: { // Set default props for specific components // @@ -91,6 +94,12 @@ const App = (props) => { Date: Mon, 2 Dec 2024 16:42:49 +0800 Subject: [PATCH 02/13] feat(react/Popper): use the `useDefaultProps` hook for managing default props --- packages/react/src/popper/Popper.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react/src/popper/Popper.js b/packages/react/src/popper/Popper.js index e2b93d2124..ebb3afc423 100644 --- a/packages/react/src/popper/Popper.js +++ b/packages/react/src/popper/Popper.js @@ -1,6 +1,7 @@ import { createPopper } from '@popperjs/core'; import { useEffectOnce } from '@tonic-ui/react-hooks'; import React, { forwardRef, useEffect, useRef, useState, useCallback } from 'react'; +import { useDefaultProps } from '../default-props'; import { Portal } from '../portal'; import { Box } from '../box'; import { assignRef } from '../utils/refs'; @@ -11,8 +12,8 @@ function getAnchorEl(anchorEl) { const defaultPlacement = 'bottom-start'; -const Popper = forwardRef(( - { +const Popper = forwardRef((inProps, ref) => { + const { anchorEl, // TODO: rename to `referenceRef` in a future release children, isOpen, @@ -24,9 +25,8 @@ const Popper = forwardRef(( usePortal = false, willUseTransition = false, ...rest - }, - ref, -) => { + } = useDefaultProps({ props: inProps, name: 'Popper' }); + const nodeRef = useRef(); const popperRef = useRef(null); // popper instance const [exited, setExited] = useState(true); From 3f44d71f62d424697423b787b4d01e988338408c Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Mon, 2 Dec 2024 17:56:53 +0800 Subject: [PATCH 03/13] docs: shadow DOM integration --- packages/react-docs/config/sidebar-routes.js | 10 + .../pages/customization/shadow-dom.js | 402 ++++++++++++++++++ .../pages/customization/shadow-dom.page.mdx | 140 ++++++ 3 files changed, 552 insertions(+) create mode 100644 packages/react-docs/pages/customization/shadow-dom.js create mode 100644 packages/react-docs/pages/customization/shadow-dom.page.mdx diff --git a/packages/react-docs/config/sidebar-routes.js b/packages/react-docs/config/sidebar-routes.js index 7532419034..e2fdd07494 100644 --- a/packages/react-docs/config/sidebar-routes.js +++ b/packages/react-docs/config/sidebar-routes.js @@ -10,6 +10,7 @@ import { MigrateSuccessIcon, RocketIcon, SVGIcon, + ToolsConfigurationIcon, UserTeamIcon, WidgetsIcon, WorkspaceIcon, @@ -61,6 +62,15 @@ export const routes = [ { title: 'React Icons', path: 'contributing/react-icons' }, ], }, + { + title: 'Customization', + icon: (props) => ( + + ), + routes: [ + { title: 'Shadow DOM', path: 'customization/shadow-dom' }, + ], + }, { title: 'Migrations', icon: (props) => ( diff --git a/packages/react-docs/pages/customization/shadow-dom.js b/packages/react-docs/pages/customization/shadow-dom.js new file mode 100644 index 0000000000..e374e0e594 --- /dev/null +++ b/packages/react-docs/pages/customization/shadow-dom.js @@ -0,0 +1,402 @@ +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +import { + Box, + Button, + Divider, + Drawer, + DrawerOverlay, + DrawerContent, + DrawerHeader, + DrawerBody, + DrawerFooter, + Flex, + Grid, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + PortalManager, + Skeleton, + Stack, + Text, + Toast, + ToastManager, + TonicProvider, + Tooltip, + createTheme, + useColorMode, + usePortalManager, + useToastManager, +} from '@tonic-ui/react'; +import BorderedBox from '@/components/BorderedBox'; +import React, { useEffect, useRef } from 'react'; +import { createRoot } from 'react-dom/client'; + +const NONCE = process.env.NONCE ?? ''; + +const DrawerComponent = ({ onClose }) => { + return ( + + + + + Drawer + + + + + + + + + + + + + + + + + ); +}; + +const ModalComponent = ({ onClose }) => { + return ( + + + + + Modal + + + + + + + + + + + + + + + + + ); +}; + +const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { + const containerRef = useRef(); + const shadowRootElementRef = useRef(); + + useEffect(() => { + const container = containerRef.current; + if (!container) { + return; + } + + const shadowContainer = container.shadowRoot ?? container.attachShadow({ mode: 'open' }); + const shadowRootElement = document.createElement('div'); + shadowContainer.appendChild(shadowRootElement); + shadowRootElementRef.current = shadowRootElement; + + return () => { + // Empty the shadow DOM content + if (shadowContainer) { + shadowContainer.innerHTML = ''; + } + + shadowRootElementRef.current = null; + }; + }, []); // Run only once on mount + + useEffect(() => { + const shadowRootElement = shadowRootElementRef.current; + const shadowContainer = shadowRootElement.parentNode; + const root = createRoot(shadowRootElement); + const cache = createCache({ + key: 'tonic-ui-shadow', + nonce: NONCE, // Needed to comply with Content Security Policy (CSP) for inline execution + prepend: true, + container: shadowContainer, + }); + const shadowTheme = createTheme({ + cssVariables: { + prefix: 'tonic-shadow', + rootSelector: ':host', + }, + components: { + Drawer: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + Modal: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + Popper: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + }, + }); + + root.render( + + + &:first-of-type': { + mt: '4x', // the space to the top edge of the screen + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: '4x', // the space to the bottom edge of the screen + }, + '[data-toast-placement$="left"] > &': { + ml: '4x', // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: '4x', // the space to the right edge of the screen + }, + }, + }} + containerRef={shadowRootElementRef} + > + + {children} + + + + + ); + }, [children, colorMode]); + + return ( +
+ ); +}; + +const InsideShadowDOMComponent = () => { + const portal = usePortalManager(); + const toast = useToastManager(); + const handleClickDrawer = () => { + portal((close) => ); + }; + const handleClickModal = () => { + portal((close) => ); + }; + const handleClickToast = () => { + const render = ({ id, onClose, placement }) => { + const isTop = placement.includes('top'); + const toastSpacingKey = isTop ? 'pb' : 'pt'; + const styleProps = { + [toastSpacingKey]: '2x', + width: 320, + }; + return ( + + + This is a toast notification + + + ); + }; + const options = { + duration: 5000, + }; + toast(render, options); + }; + + return ( + + + Inside Shadow DOM + + + + + + + + + + + + ); +}; + +const OutsideShadowDOMComponent = () => { + const portal = usePortalManager(); + const toast = useToastManager(); + const handleClickDrawer = () => { + portal((close) => ); + }; + const handleClickModal = () => { + portal((close) => ); + }; + const handleClickToast = () => { + const render = ({ id, onClose, placement }) => { + const isTop = placement.includes('top'); + const toastSpacingKey = isTop ? 'pb' : 'pt'; + const styleProps = { + [toastSpacingKey]: '2x', + width: 320, + }; + return ( + + + This is a toast notification + + + ); + }; + const options = { + duration: 5000, + }; + toast(render, options); + }; + + return ( + + + Outside Shadow DOM + + + + + + + + + + + + ); +}; + +const App = () => { + const [colorMode] = useColorMode(); + + return ( + + + + + + + + + ); +}; + +export default App; diff --git a/packages/react-docs/pages/customization/shadow-dom.page.mdx b/packages/react-docs/pages/customization/shadow-dom.page.mdx new file mode 100644 index 0000000000..d75793a76e --- /dev/null +++ b/packages/react-docs/pages/customization/shadow-dom.page.mdx @@ -0,0 +1,140 @@ +# Shadow DOM + +The shadow DOM allows you to encapsulate parts of your application, isolating them from global styles and preventing interference with the regular DOM tree. + +## Shadow DOM Integration + +To enable styling within the shadow DOM, start by importing `createCache` and `CacheProvider`: + +```js +import createCache from '@emotion/cache'; +import { CacheProvider } from '@emotion/react'; +``` + +### 1. Applying styles inside the shadow DOM + +The shadow DOM creates an isolated DOM tree attached to a host element, helping prevent style conflicts across components. Below is an example of how to create and style a shadow DOM: + +```js +const container = document.querySelector('#root'); +const shadowContainer = container.shadowRoot || container.attachShadow({ mode: 'open' }); +const shadowRootElement = document.createElement('div'); +shadowContainer.appendChild(shadowRootElement); + +// Consider using `const shadowRootElementRef = useRef();` if working within a functional component +const shadowRootElementRef = { + current: shadowRootElement, +}; + +const cache = createCache({ + key: 'tonic-ui-shadow', // or 'css' + prepend: true, + container: shadowContainer, + nonce, // [optional] to comply with Content Security Policy (CSP) for inline execution +}); +``` + +### 2. Theming components inside the shadow DOM + +Components such as `Drawer`, `Modal`, and `Popper` from Tonic UI often render outside the main DOM hierarchy using a `Portal`. By default, these portals render in `document.body`. When using the shadow DOM, they need to render within the shadow container: + +```js +const shadowTheme = createTheme({ + components: { + Drawer: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + Modal: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + Popper: { + defaultProps: { + portalProps: { + containerRef: shadowRootElementRef, + }, + }, + }, + }, +}); +``` + +### 3. Using CSS theme variables (optional) + +To use CSS theme variables within the shadow DOM, specify the root selector for generating the CSS variables: + +```js +const shadowTheme = createTheme({ + cssVariables: { + prefix: 'tonic-shadow', + rootSelector: ':host', + }, + components: { + // Same as the above step + }, +}); +``` + +### 4. Rendering components inside the shadow DOM + +The following code snippet illustrates how to render a React application inside the shadow DOM: + +```jsx +React.createRoot(shadowRootElement).render( + + + &:first-of-type': { + mt: '4x', // the space to the top edge of the screen + }, + '[data-toast-placement^="bottom"] > &:last-of-type': { + mb: '4x', // the space to the bottom edge of the screen + }, + '[data-toast-placement$="left"] > &': { + ml: '4x', // the space to the left edge of the screen + }, + '[data-toast-placement$="right"] > &': { + mr: '4x', // the space to the right edge of the screen + }, + }, + }} + containerRef={shadowRootElementRef} + > + + + + + + +); +``` + +## Demo + +This example applies a global button style. The button outside the shadow DOM inherits the global styling, while the button inside the shadow DOM remains unaffected: + +```jsx +sx={{ + 'button': { + opacity: '.65 !important', + }, +}} +``` + +{render('./shadow-dom')} From 74024b26ee65a2645c1bf38b3d02551b198d54a8 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Mon, 2 Dec 2024 18:02:50 +0800 Subject: [PATCH 04/13] chore(changeset): create `tonic-ui-943.md` --- .changeset/tonic-ui-943.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tonic-ui-943.md diff --git a/.changeset/tonic-ui-943.md b/.changeset/tonic-ui-943.md new file mode 100644 index 0000000000..b5614d03ce --- /dev/null +++ b/.changeset/tonic-ui-943.md @@ -0,0 +1,5 @@ +--- +"@tonic-ui/react": patch +--- + +feat(react/Popper): use the `useDefaultProps` hook for managing default props From 2be841546151e42a79953cfb918149151a0ffb2a Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Tue, 3 Dec 2024 11:32:35 +0800 Subject: [PATCH 05/13] refactor: refine shadow DOM cleanup logic --- packages/react-docs/pages/_app.page.js | 2 +- .../pages/customization/shadow-dom.js | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/react-docs/pages/_app.page.js b/packages/react-docs/pages/_app.page.js index 252f564358..33c41e390a 100644 --- a/packages/react-docs/pages/_app.page.js +++ b/packages/react-docs/pages/_app.page.js @@ -38,7 +38,7 @@ const EmotionCacheProvider = ({ nonce, }) => { const cache = createCache({ - key: 'tonic-ui', + key: 'tonic-css', nonce, }); diff --git a/packages/react-docs/pages/customization/shadow-dom.js b/packages/react-docs/pages/customization/shadow-dom.js index e374e0e594..88b360b5d1 100644 --- a/packages/react-docs/pages/customization/shadow-dom.js +++ b/packages/react-docs/pages/customization/shadow-dom.js @@ -121,6 +121,7 @@ const ModalComponent = ({ onClose }) => { const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { const containerRef = useRef(); const shadowRootElementRef = useRef(); + const rootRef = useRef(); useEffect(() => { const container = containerRef.current; @@ -132,23 +133,28 @@ const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { const shadowRootElement = document.createElement('div'); shadowContainer.appendChild(shadowRootElement); shadowRootElementRef.current = shadowRootElement; + rootRef.current = createRoot(shadowRootElement); return () => { - // Empty the shadow DOM content - if (shadowContainer) { - shadowContainer.innerHTML = ''; - } - - shadowRootElementRef.current = null; + setTimeout(() => { + rootRef.current.unmount(); // Clean up React state + rootRef.current = null; + shadowRootElementRef.current = null; // Clear the reference + shadowContainer.replaceChildren(); // Clear the shadow DOM content + }, 0); }; }, []); // Run only once on mount useEffect(() => { const shadowRootElement = shadowRootElementRef.current; + if (!(shadowRootElement instanceof HTMLElement)) { + console.error('The shadow root element is not an HTMLElement.'); + return; + } const shadowContainer = shadowRootElement.parentNode; - const root = createRoot(shadowRootElement); + const cache = createCache({ - key: 'tonic-ui-shadow', + key: 'tonic-shadow-css', nonce: NONCE, // Needed to comply with Content Security Policy (CSP) for inline execution prepend: true, container: shadowContainer, @@ -183,6 +189,7 @@ const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { }, }); + const root = rootRef.current; root.render( Date: Tue, 3 Dec 2024 11:34:09 +0800 Subject: [PATCH 06/13] docs: remove the `as="p"` from `ParagraphComponent` to allow more flexible usage --- packages/react-docs/components/MDXComponents.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-docs/components/MDXComponents.js b/packages/react-docs/components/MDXComponents.js index bd92da2a1e..5ebb13014a 100644 --- a/packages/react-docs/components/MDXComponents.js +++ b/packages/react-docs/components/MDXComponents.js @@ -16,7 +16,6 @@ import { codeBlockLight, codeBlockDark } from '../prism-themes/tonic-ui'; const ParagraphComponent = props => ( { /> ); })` - > p { + > * { margin-bottom: 0; } `; From d54ab26b06904d9d39c2797f33671c153dd90f5a Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Tue, 3 Dec 2024 18:35:43 +0800 Subject: [PATCH 07/13] docs: update the "Shadow DOM" page --- .../pages/customization/shadow-dom.js | 21 ++++++++---- .../pages/customization/shadow-dom.page.mdx | 33 ++++++++++++------- .../getting-started/usage/index.page.mdx | 1 + 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/packages/react-docs/pages/customization/shadow-dom.js b/packages/react-docs/pages/customization/shadow-dom.js index 88b360b5d1..00472833f3 100644 --- a/packages/react-docs/pages/customization/shadow-dom.js +++ b/packages/react-docs/pages/customization/shadow-dom.js @@ -153,15 +153,17 @@ const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { } const shadowContainer = shadowRootElement.parentNode; + // https://emotion.sh/docs/@emotion/cache const cache = createCache({ - key: 'tonic-shadow-css', + key: 'tonic-css', nonce: NONCE, // Needed to comply with Content Security Policy (CSP) for inline execution prepend: true, container: shadowContainer, }); + const shadowTheme = createTheme({ cssVariables: { - prefix: 'tonic-shadow', + prefix: 'tonic', rootSelector: ':host', }, components: { @@ -186,6 +188,16 @@ const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { }, }, }, + PortalManager: { + defaultProps: { + containerRef: shadowRootElementRef, + }, + }, + ToastManager: { + defaultProps: { + containerRef: shadowRootElementRef, + }, + }, }, }); @@ -215,11 +227,8 @@ const ShadowDOMContainer = ({ children, colorMode, ...rest }) => { }, }, }} - containerRef={shadowRootElementRef} > - + {children} diff --git a/packages/react-docs/pages/customization/shadow-dom.page.mdx b/packages/react-docs/pages/customization/shadow-dom.page.mdx index d75793a76e..b6dbb57d3b 100644 --- a/packages/react-docs/pages/customization/shadow-dom.page.mdx +++ b/packages/react-docs/pages/customization/shadow-dom.page.mdx @@ -21,22 +21,25 @@ const shadowContainer = container.shadowRoot || container.attachShadow({ mode: ' const shadowRootElement = document.createElement('div'); shadowContainer.appendChild(shadowRootElement); -// Consider using `const shadowRootElementRef = useRef();` if working within a functional component +// If you're using a functional component, you might want to use `useRef` to store the `shadowRootElement`. const shadowRootElementRef = { current: shadowRootElement, }; +// https://emotion.sh/docs/@emotion/cache const cache = createCache({ - key: 'tonic-ui-shadow', // or 'css' + key: 'tonic-css', prepend: true, container: shadowContainer, - nonce, // [optional] to comply with Content Security Policy (CSP) for inline execution + nonce: process.env.CSP_NONCE || 'your-nonce-string', // [optional] to comply with Content Security Policy (CSP) for inline execution }); ``` ### 2. Theming components inside the shadow DOM -Components such as `Drawer`, `Modal`, and `Popper` from Tonic UI often render outside the main DOM hierarchy using a `Portal`. By default, these portals render in `document.body`. When using the shadow DOM, they need to render within the shadow container: +`PortalManager`, `ToastManager`, and components such as `Drawer`, `Modal`, and `Popper` render outside the main DOM hierarchy through a `Portal`, which defaults to `document.body`. To maintain consistent theming inside the shadow DOM, proper configuration is needed. + +Below is an example configuration using a custom theme: ```js const shadowTheme = createTheme({ @@ -62,6 +65,16 @@ const shadowTheme = createTheme({ }, }, }, + PortalManager: { + defaultProps: { + containerRef: shadowRootElementRef, + }, + }, + ToastManager: { + defaultProps: { + containerRef: shadowRootElementRef, + }, + }, }, }); ``` @@ -73,7 +86,7 @@ To use CSS theme variables within the shadow DOM, specify the root selector for ```js const shadowTheme = createTheme({ cssVariables: { - prefix: 'tonic-shadow', + prefix: 'tonic', rootSelector: ':host', }, components: { @@ -84,10 +97,10 @@ const shadowTheme = createTheme({ ### 4. Rendering components inside the shadow DOM -The following code snippet illustrates how to render a React application inside the shadow DOM: +The following code snippet demonstrates how to render components inside the shadow DOM: ```jsx -React.createRoot(shadowRootElement).render( +ReactDOM.createRoot(shadowRootElement).render( &:first-of-type': { @@ -112,11 +126,8 @@ React.createRoot(shadowRootElement).render( }, }, }} - containerRef={shadowRootElementRef} > - + diff --git a/packages/react-docs/pages/getting-started/usage/index.page.mdx b/packages/react-docs/pages/getting-started/usage/index.page.mdx index e055669beb..c7863d5e95 100644 --- a/packages/react-docs/pages/getting-started/usage/index.page.mdx +++ b/packages/react-docs/pages/getting-started/usage/index.page.mdx @@ -75,6 +75,7 @@ function App(props) { useCSSBaseline={true} // If `true`, apply CSS reset and base styles > &:first-of-type': { From 53a513fdd11b559a6bbfe2b3b2a1de6e5fe4cdec Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 12:11:35 +0800 Subject: [PATCH 08/13] docs: add "CSS Theme Variables" page --- packages/react-docs/config/sidebar-routes.js | 2 +- .../css-theme-variables.js} | 32 +++---- .../css-theme-variables.page.mdx | 87 +++++++++++++++++++ .../css-variables/index.page.mdx | 42 --------- 4 files changed, 104 insertions(+), 59 deletions(-) rename packages/react-docs/pages/{getting-started/css-variables/default-css-variables.js => customization/css-theme-variables.js} (51%) create mode 100644 packages/react-docs/pages/customization/css-theme-variables.page.mdx delete mode 100644 packages/react-docs/pages/getting-started/css-variables/index.page.mdx diff --git a/packages/react-docs/config/sidebar-routes.js b/packages/react-docs/config/sidebar-routes.js index e2fdd07494..42d1428b2b 100644 --- a/packages/react-docs/config/sidebar-routes.js +++ b/packages/react-docs/config/sidebar-routes.js @@ -34,7 +34,6 @@ export const routes = [ { title: 'Usage', path: 'getting-started/usage' }, { title: 'Color Mode', path: 'getting-started/color-mode' }, { title: 'Color Style', path: 'getting-started/color-style' }, - { title: 'CSS Variables', path: 'getting-started/css-variables' }, { title: 'Icons', path: 'getting-started/icons' }, { title: 'The sx prop', path: 'getting-started/the-sx-prop' }, { title: 'Security', path: 'getting-started/security' }, @@ -68,6 +67,7 @@ export const routes = [ ), routes: [ + { title: 'CSS Theme Variables', path: 'customization/css-theme-variables' }, { title: 'Shadow DOM', path: 'customization/shadow-dom' }, ], }, diff --git a/packages/react-docs/pages/getting-started/css-variables/default-css-variables.js b/packages/react-docs/pages/customization/css-theme-variables.js similarity index 51% rename from packages/react-docs/pages/getting-started/css-variables/default-css-variables.js rename to packages/react-docs/pages/customization/css-theme-variables.js index 170858872d..2c2675752e 100644 --- a/packages/react-docs/pages/getting-started/css-variables/default-css-variables.js +++ b/packages/react-docs/pages/customization/css-theme-variables.js @@ -17,23 +17,23 @@ const App = () => { return ( - {Object.entries(theme?.cssVariables).map(([name, value]) => { - if (!name.startsWith('--')) { - return null; - } - - return ( - - {name}: - - {isColorCode(value) && ( - - )} - {value}; + {':root {'} + + {Object.entries(theme?.cssVariables).map(([name, value]) => { + return ( + + {name}: + + {isColorCode(value) && ( + + )} + {value}; + - - ); - })} + ); + })} + + {'}'} ); }; diff --git a/packages/react-docs/pages/customization/css-theme-variables.page.mdx b/packages/react-docs/pages/customization/css-theme-variables.page.mdx new file mode 100644 index 0000000000..4d80e3e54b --- /dev/null +++ b/packages/react-docs/pages/customization/css-theme-variables.page.mdx @@ -0,0 +1,87 @@ +# CSS Theme Variables + +Learn how to adopt CSS theme variables. + +## Getting Started + +To use CSS theme variables, create a theme with `cssVariables: true` and wrap your app with `TonicProvider`: + +```jsx +import { + TonicProvider, + createTheme, +} from '@tonic-ui/react'; + +// `createTheme` was introduced in v2.5.0 +const theme = createTheme({ cssVariables: true }); + +function App() { + return ( + + {/* Your app content */} + + ); +} +``` + +After rendering, you will see all CSS theme variables in the `:root` stylesheet of the HTML document. By default, these variables are prefixed with `--tonic`. You can open the **Developer Tools** and go to the **Elements** tab to see all the CSS theme variables being used on the webpage. + +{render('./css-theme-variables')} + +## Customizing Variable Prefix + +You can customize the variable prefix by providing a string to the `prefix` property in the theme configuration. + +```js +createTheme({ + cssVariables: { + prefix: 'custom', + }, +}); +``` + +```css +:root { + --custom-borders-1: .0625rem solid; + --custom-borders-2: .125rem solid; + /* More variables */ +} +``` + +If you prefer not to use any prefix, set `prefix` to an empty string: + +```js +createTheme({ + cssVariables: { + prefix: '', + }, +}); +``` + +```css +:root { + --borders-1: .0625rem solid; + --borders-2: .125rem solid; + /* More variables */ +} +``` + +## Variables Inside Shadow DOM + +To apply CSS theme variables inside shadow DOM, specify a different root selector using the `rootSelector` option: + +```js +createTheme({ + cssVariables: { + rootSelector: ':host', + }, +}); +``` + +```css +:host { + --tonic-borders-1: .0625rem solid; + --tonic-borders-2: .125rem solid; + /* More variables */ +} +``` diff --git a/packages/react-docs/pages/getting-started/css-variables/index.page.mdx b/packages/react-docs/pages/getting-started/css-variables/index.page.mdx deleted file mode 100644 index bff7624152..0000000000 --- a/packages/react-docs/pages/getting-started/css-variables/index.page.mdx +++ /dev/null @@ -1,42 +0,0 @@ -# CSS Variables - -## Overview - -Tonic UI supports converting theme tokens defined in the theme to CSS variables. You can enable this behavior by configuring the theme as shown below: - -```jsx -// Use `createTheme` to provide custom configuration - -``` - -For example, consider a theme object that looks like this: - -```js -const theme = { - colors: { - 'gray:10': '#fafafa', - 'gray:20': '#f7f7f7', - }, -}; -``` - -When this theme is passed to `TonicProvider`, Tonic UI will generate CSS variables automatically, as shown below: - -```css -:root { - --tonic-colors-gray-10: #fafafa; - --tonic-colors-gray-20: #f7f7f7; -} -``` - -These CSS variables can then be used to style components, providing a consistent look and feel throughout the application. - -## Default CSS Variables - -If you want to see all the CSS variables being used on the webpage, you can open the **Developer Tools** and go to the **Elements** tab. There, you will be able to view all the default CSS variables. - -{render('./default-css-variables')} From 1626df3c4f8d21dc42f5d9db00875bc41385fc83 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 14:20:22 +0800 Subject: [PATCH 09/13] docs: update docs --- .../react-docs/components/MDXComponents.js | 15 ++++++----- .../css-theme-variables.page.mdx | 26 ++++++++++++++++++- .../getting-started/usage/index.page.mdx | 15 ++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/react-docs/components/MDXComponents.js b/packages/react-docs/components/MDXComponents.js index 5ebb13014a..599331a555 100644 --- a/packages/react-docs/components/MDXComponents.js +++ b/packages/react-docs/components/MDXComponents.js @@ -1,4 +1,3 @@ -import styled from '@emotion/styled'; import { Box, Code, @@ -141,7 +140,7 @@ const H6Component = props => { ); }; -const BlockquoteComponent = styled(props => { +const BlockquoteComponent = props => { const [colorMode] = useColorMode(); const [colorStyle] = useColorStyle({ colorMode }); const backgroundColor = { @@ -167,14 +166,16 @@ const BlockquoteComponent = styled(props => { mb="4x" px="4x" py="3x" + sx={{ + // The "ParagraphComponent" was changed to "div" instead of "p" + '> div': { + marginBottom: 0, + }, + }} {...props} /> ); -})` - > * { - margin-bottom: 0; - } -`; +}; const UnorderedListComponent = props => ( + {/* Your app content */} + + ); +} +``` + After rendering, you will see all CSS theme variables in the `:root` stylesheet of the HTML document. By default, these variables are prefixed with `--tonic`. You can open the **Developer Tools** and go to the **Elements** tab to see all the CSS theme variables being used on the webpage. {render('./css-theme-variables')} diff --git a/packages/react-docs/pages/getting-started/usage/index.page.mdx b/packages/react-docs/pages/getting-started/usage/index.page.mdx index c7863d5e95..ce147cd910 100644 --- a/packages/react-docs/pages/getting-started/usage/index.page.mdx +++ b/packages/react-docs/pages/getting-started/usage/index.page.mdx @@ -1,5 +1,7 @@ # Usage +> The `createTheme` function was introduced in v2.5.0. If you're using a version prior to v2.5.0, please refer to the [v2.4.0 documentation](https://trendmicro-frontend.github.io/tonic-ui/react/2.4.0/getting-started/usage). + You can use any of the components as demonstrated in the documentation. If you are the first time using Tonic UI, you may want to read the following sections to get started. ## Setup Provider @@ -40,14 +42,14 @@ import { ToastManager, // Manages toasts in the application TonicProvider, // Provides theme and context for Tonic UI components colorStyle, // Default color style object - createTheme, // For theme customization (introduced in v2.5.0) + createTheme, // For theme customization useColorMode, // Hook to manage color mode (e.g., light/dark) useColorStyle, // Hook to manage color style useTheme, // Hook to access the theme object } from '@tonic-ui/react'; const customTheme = createTheme({ - cssVariables: true, // Enable CSS variables replacement + cssVariables: true, // Enable CSS theme variables components: { // Set default props for specific components // @@ -183,14 +185,14 @@ Sometimes you may need to apply base CSS styles to your application. Tonic UI pr > If you are not writing an application, you may want to set `useCSSBaseline` to `false` (or not set it at all) to prevent global styles pollution. -### Enabling CSS variables +### Enabling CSS theme variables Tonic UI supports converting theme tokens defined in the theme to CSS variables. You can enable this behavior by configuring the theme as shown below: ```jsx ``` @@ -294,12 +296,11 @@ Override the theme object and pass it to the `` component. ```js import { - createTheme, // For theme customization (introduced in v2.5.0) + createTheme, // For theme customization } from '@tonic-ui/react'; -// Let's say you want to add more colors const customTheme = createTheme({ - cssVariables: true, // Enable CSS variables replacement + cssVariables: true, // Enable CSS theme variables colors: { brand: { 90: "#1a365d", From 7c20fcff499dcbb77bb77d91eebe155f64c37cdc Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 14:47:02 +0800 Subject: [PATCH 10/13] docs: move "Content Security Policy" to the "Customization" section --- packages/react-docs/config/sidebar-routes.js | 2 +- .../content-security-policy.page.mdx} | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) rename packages/react-docs/pages/{getting-started/security/index.page.mdx => customization/content-security-policy.page.mdx} (57%) diff --git a/packages/react-docs/config/sidebar-routes.js b/packages/react-docs/config/sidebar-routes.js index 42d1428b2b..2e706cd90c 100644 --- a/packages/react-docs/config/sidebar-routes.js +++ b/packages/react-docs/config/sidebar-routes.js @@ -36,7 +36,6 @@ export const routes = [ { title: 'Color Style', path: 'getting-started/color-style' }, { title: 'Icons', path: 'getting-started/icons' }, { title: 'The sx prop', path: 'getting-started/the-sx-prop' }, - { title: 'Security', path: 'getting-started/security' }, { title: 'Tonic UI Versions', path: 'getting-started/versions' }, ], }, @@ -67,6 +66,7 @@ export const routes = [ ), routes: [ + { title: 'Content Security Policy', path: 'customization/content-security-policy' }, { title: 'CSS Theme Variables', path: 'customization/css-theme-variables' }, { title: 'Shadow DOM', path: 'customization/shadow-dom' }, ], diff --git a/packages/react-docs/pages/getting-started/security/index.page.mdx b/packages/react-docs/pages/customization/content-security-policy.page.mdx similarity index 57% rename from packages/react-docs/pages/getting-started/security/index.page.mdx rename to packages/react-docs/pages/customization/content-security-policy.page.mdx index 2050df83e1..60eba97a35 100644 --- a/packages/react-docs/pages/getting-started/security/index.page.mdx +++ b/packages/react-docs/pages/customization/content-security-policy.page.mdx @@ -1,10 +1,10 @@ -# Security +# Content Security Policy -## Content Security Policy +Content Security Policy plays a critical role in protecting against various attacks, most notably Cross-Site Scripting (XSS) and data injections. Its core function involves the inclusion of either a `Content-Security-Policy` header in the HTTP response or `` tags within the HTML of a page. -Content Security Policy (CSP) plays a critical role in protecting against various attacks, most notably Cross-Site Scripting (XSS) and data injections. Its core function involves the inclusion of either a `Content-Security-Policy` header in the HTTP response or `` tags within the HTML of a page. +## Getting Started -Tonic UI relies on [Emotion](https://emotion.sh/) for its styling system. To seamless integrate [Emotion](https://emotion.sh/) with CSP, it is essential to provide a `nonce` value to the `CacheProvider` component. Detailed instruction can be found in the [Emotion documentation](https://emotion.sh/docs/@emotion/cache). +Tonic UI relies on [Emotion](https://emotion.sh/) for its styling system. To seamless integrate [Emotion](https://emotion.sh/) with Content Security Policy, it is essential to provide a `nonce` value to the `CacheProvider` component. Detailed instruction can be found in the [Emotion documentation](https://emotion.sh/docs/@emotion/cache). ### Step 1: Implement a `EmotionCacheProvider` component @@ -13,7 +13,11 @@ const EmotionCacheProvider = ({ children, nonce, }) => { - const cache = createCache({ key: 'tonic-ui', nonce }); + const cache = createCache({ + key: 'tonic-ui', + nonce, + }); + return ( {children} From 02fbe3efa70bcbbe4c507c3955d4155e6c989155 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 15:25:46 +0800 Subject: [PATCH 11/13] feat(react): export `DefaultPropsProvider` and `useDefaultProps` --- packages/react/__tests__/index.test.js | 4 ++++ packages/react/src/index.js | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/react/__tests__/index.test.js b/packages/react/__tests__/index.test.js index d9a7f936c9..1221a4e6de 100644 --- a/packages/react/__tests__/index.test.js +++ b/packages/react/__tests__/index.test.js @@ -64,6 +64,10 @@ test('should match expected exports', () => { 'Calendar', 'DatePicker', + // default-props + 'DefaultPropsProvider', + 'useDefaultProps', + // divider 'Divider', diff --git a/packages/react/src/index.js b/packages/react/src/index.js index a9e79719e3..7eb2ed63bd 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -10,6 +10,7 @@ export * from './color-mode'; export * from './color-style'; export * from './css-baseline'; export * from './date-pickers'; +export * from './default-props'; export * from './divider'; export * from './drawer'; export * from './flex'; From 954d1326b1e3d51e2334b9daaaf31bb940af393b Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 15:34:57 +0800 Subject: [PATCH 12/13] chore: update changeset files --- .changeset/tonic-ui-943a.md | 5 +++++ .changeset/{tonic-ui-943.md => tonic-ui-943b.md} | 0 2 files changed, 5 insertions(+) create mode 100644 .changeset/tonic-ui-943a.md rename .changeset/{tonic-ui-943.md => tonic-ui-943b.md} (100%) diff --git a/.changeset/tonic-ui-943a.md b/.changeset/tonic-ui-943a.md new file mode 100644 index 0000000000..675dfec786 --- /dev/null +++ b/.changeset/tonic-ui-943a.md @@ -0,0 +1,5 @@ +--- +"@tonic-ui/react": patch +--- + +feat(react): add exports for `DefaultPropsProvider` and `useDefaultProps` diff --git a/.changeset/tonic-ui-943.md b/.changeset/tonic-ui-943b.md similarity index 100% rename from .changeset/tonic-ui-943.md rename to .changeset/tonic-ui-943b.md From 9fb936460760c5ad4d1c5988363b9d0bb321e000 Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Wed, 4 Dec 2024 15:56:24 +0800 Subject: [PATCH 13/13] docs: update customization pages --- .../pages/customization/content-security-policy.page.mdx | 4 ++-- packages/react-docs/pages/customization/shadow-dom.page.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-docs/pages/customization/content-security-policy.page.mdx b/packages/react-docs/pages/customization/content-security-policy.page.mdx index 60eba97a35..be58c77b91 100644 --- a/packages/react-docs/pages/customization/content-security-policy.page.mdx +++ b/packages/react-docs/pages/customization/content-security-policy.page.mdx @@ -6,7 +6,7 @@ Content Security Policy plays a critical role in protecting against various atta Tonic UI relies on [Emotion](https://emotion.sh/) for its styling system. To seamless integrate [Emotion](https://emotion.sh/) with Content Security Policy, it is essential to provide a `nonce` value to the `CacheProvider` component. Detailed instruction can be found in the [Emotion documentation](https://emotion.sh/docs/@emotion/cache). -### Step 1: Implement a `EmotionCacheProvider` component +### 1. Implement a `EmotionCacheProvider` component ```jsx const EmotionCacheProvider = ({ @@ -26,7 +26,7 @@ const EmotionCacheProvider = ({ }; ``` -### Step 2: Integrate the `EmotionCacheProvider` component with `TonicProvider` +### 2. Integrate the `EmotionCacheProvider` component with `TonicProvider` Wrap the `TonicProvider` component with the `EmotionCacheProvider` and provide the relevant `nonce` value. This value will be utilized by [Emotion](https://emotion.sh/) to generate a style tag with the necessary `nonce` attribute. diff --git a/packages/react-docs/pages/customization/shadow-dom.page.mdx b/packages/react-docs/pages/customization/shadow-dom.page.mdx index b6dbb57d3b..cc8b05244d 100644 --- a/packages/react-docs/pages/customization/shadow-dom.page.mdx +++ b/packages/react-docs/pages/customization/shadow-dom.page.mdx @@ -2,7 +2,7 @@ The shadow DOM allows you to encapsulate parts of your application, isolating them from global styles and preventing interference with the regular DOM tree. -## Shadow DOM Integration +## Getting Started To enable styling within the shadow DOM, start by importing `createCache` and `CacheProvider`: