From 22fb2f3dc71833b62173005bc81b52e88e24c3b1 Mon Sep 17 00:00:00 2001 From: Charles-Antoine Alba Date: Thu, 2 Oct 2025 12:44:03 +0200 Subject: [PATCH 1/4] feat(theme-generator): implement the component inside Storybook with corresponding stories --- package.json | 3 +- packages/storybook/.storybook/main.ts | 6 +- .../src/components/storyGrid/StoryGrid.tsx | 110 +++++++++ .../components/storyGrid/storyGrid.module.css | 5 + .../themeGenerator/ThemeGenerator.tsx | 183 +++++++++++++++ .../ThemeGeneratorTreeView.tsx | 96 ++++++++ .../themeGeneratorTreeView.module.css | 27 +++ .../themeGenerator/themeGenerator.module.css | 58 +++++ .../themeGeneratorJSON/ThemeGeneratorJSON.tsx | 86 +++++++ .../themeGeneratorJSON.module.css | 29 +++ .../ThemeGeneratorModal.tsx | 65 ++++++ .../themeGeneratorModal.module.css | 8 + .../ThemeGeneratorPreview.tsx | 59 +++++ .../ThemeGeneratorTreeView.tsx | 96 ++++++++ .../themeGeneratorTreeView.module.css | 27 +++ .../themeGenerator/useThemeGenerator.ts | 74 ++++++ .../accordion/accordion.stories.tsx | 27 +++ .../components/badge/badge.stories.tsx | 20 ++ .../breadcrumb/breadcrumb.stories.tsx | 53 +++++ .../components/button/button.stories.tsx | 39 ++++ .../stories/components/card/card.stories.tsx | 20 ++ .../components/checkbox/checkbox.stories.tsx | 44 ++++ .../clipboard/clipboard.stories.tsx | 31 +++ .../stories/components/code/code.stories.tsx | 23 ++ .../components/combobox/combobox.stories.tsx | 36 +++ .../datepicker/datepicker.stories.tsx | 41 ++++ .../components/divider/divider.stories.tsx | 15 ++ .../components/drawer/drawer.stories.tsx | 47 ++++ .../file-upload/file-upload.stories.tsx | 23 ++ .../form-field/form-field.stories.tsx | 26 +++ .../stories/components/icon/icon.stories.tsx | 17 ++ .../components/input/input.stories.tsx | 18 ++ .../stories/components/link/link.stories.tsx | 16 ++ .../components/medium/medium.stories.tsx | 15 ++ .../components/message/message.stories.tsx | 36 +++ .../components/meter/meter.stories.tsx | 17 ++ .../components/modal/modal.stories.tsx | 38 ++++ .../pagination/pagination.stories.tsx | 16 ++ .../components/password/password.stories.tsx | 18 ++ .../phone-number/phone-number.stories.tsx | 29 +++ .../components/popover/popover.stories.tsx | 25 ++ .../progress-bar/progress-bar.stories.tsx | 17 ++ .../components/quantity/quantity.stories.tsx | 35 +++ .../radio-group/radio-group.stories.tsx | 29 +++ .../components/range/range.stories.tsx | 18 ++ .../components/select/select.stories.tsx | 36 +++ .../components/skeleton/skeleton.stories.tsx | 15 ++ .../components/spinner/spinner.stories.tsx | 17 ++ .../components/switch/switch.stories.tsx | 28 +++ .../components/table/table.stories.tsx | 33 +++ .../stories/components/tabs/tabs.stories.tsx | 20 ++ .../stories/components/tag/tag.stories.tsx | 21 ++ .../stories/components/text/text.stories.tsx | 16 ++ .../components/textarea/textarea.stories.tsx | 16 ++ .../timepicker/timepicker.stories.tsx | 28 +++ .../components/toggle/toggle.stories.tsx | 32 +++ .../components/tooltip/tooltip.stories.tsx | 25 ++ .../tree-view/tree-view.stories.tsx | 33 +++ .../tools/theme-generator.mdx | 18 ++ packages/themes/src/developer/_variables.scss | 213 ++++++++++++++++++ packages/themes/src/developer/default.scss | 17 ++ packages/themes/src/developer/fonts.scss | 9 + packages/themes/src/developer/index.scss | 123 ++++++++++ 63 files changed, 2419 insertions(+), 2 deletions(-) create mode 100644 packages/storybook/src/components/storyGrid/StoryGrid.tsx create mode 100644 packages/storybook/src/components/storyGrid/storyGrid.module.css create mode 100644 packages/storybook/src/components/themeGenerator/ThemeGenerator.tsx create mode 100644 packages/storybook/src/components/themeGenerator/ThemeGeneratorTreeView/ThemeGeneratorTreeView.tsx create mode 100644 packages/storybook/src/components/themeGenerator/ThemeGeneratorTreeView/themeGeneratorTreeView.module.css create mode 100644 packages/storybook/src/components/themeGenerator/themeGenerator.module.css create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorJSON/ThemeGeneratorJSON.tsx create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorJSON/themeGeneratorJSON.module.css create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorModal/ThemeGeneratorModal.tsx create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorModal/themeGeneratorModal.module.css create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorPreview/ThemeGeneratorPreview.tsx create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorTreeView/ThemeGeneratorTreeView.tsx create mode 100644 packages/storybook/src/components/themeGenerator/themeGeneratorTreeView/themeGeneratorTreeView.module.css create mode 100644 packages/storybook/src/components/themeGenerator/useThemeGenerator.ts create mode 100644 packages/storybook/stories/ovhcloud-design-system/tools/theme-generator.mdx create mode 100644 packages/themes/src/developer/_variables.scss create mode 100644 packages/themes/src/developer/default.scss create mode 100644 packages/themes/src/developer/fonts.scss create mode 100644 packages/themes/src/developer/index.scss diff --git a/package.json b/package.json index c58ea1258..37d706650 100644 --- a/package.json +++ b/package.json @@ -78,5 +78,6 @@ }, "resolutions": { "@types/scheduler": "< 0.23.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/packages/storybook/.storybook/main.ts b/packages/storybook/.storybook/main.ts index ec5f48656..8b68721ac 100644 --- a/packages/storybook/.storybook/main.ts +++ b/packages/storybook/.storybook/main.ts @@ -32,7 +32,11 @@ const config: StorybookConfig = { ${head} `, - staticDirs: ['../assets', ...getCodeEditorStaticDirs(__filename)], + staticDirs: [ + '../assets', + { from: '../../themes/dist', to: '/themes' }, + ...getCodeEditorStaticDirs(__filename) + ], stories: [ '../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)', diff --git a/packages/storybook/src/components/storyGrid/StoryGrid.tsx b/packages/storybook/src/components/storyGrid/StoryGrid.tsx new file mode 100644 index 000000000..b72ee505c --- /dev/null +++ b/packages/storybook/src/components/storyGrid/StoryGrid.tsx @@ -0,0 +1,110 @@ +import React, { type JSX, useEffect, useRef } from 'react'; +import styles from './storyGrid.module.css'; + +type StoryRef = { + id: string, + label?: string, +}; + +type ComponentStories = { + kind: string, + name: string, + stories: StoryRef[], +}; + +type Props = { + components: ComponentStories[], + themeClass?: string, + themeVariables?: Record, +}; + +function toTitleCase(text: string): string { + return text + .replace(/[-_]+/g, ' ') + .replace(/\b\w/g, (char) => char.toUpperCase()); +} + +function getStoryLabel(story: StoryRef): string { + if (story.label) { + return story.label; + } + + const parts = story.id.split('--'); + const suffix = parts.length > 1 ? parts[1] : story.id; + return toTitleCase(suffix); +} + +function StoryGrid({ components, themeClass, themeVariables }: Props): JSX.Element { + const iframeRefs = useRef<(HTMLIFrameElement | null)[]>([]); + + const injectThemeIntoIframe = (iframe: HTMLIFrameElement) => { + if (!iframe?.contentDocument || !themeVariables || !themeClass) return; + + const styleId = 'theme-generator-variables'; + let styleElement = iframe.contentDocument.getElementById(styleId) as HTMLStyleElement; + + if (!styleElement) { + styleElement = iframe.contentDocument.createElement('style'); + styleElement.id = styleId; + iframe.contentDocument.head.appendChild(styleElement); + } + + const cssText = `.${themeClass} {\n${Object.entries(themeVariables) + .map(([key, value]) => ` ${key}: ${value};`) + .join('\n')}\n}`; + + styleElement.textContent = cssText; + iframe.contentDocument.body.classList.add(themeClass); + }; + + useEffect(() => { + iframeRefs.current.forEach((iframe) => { + if (iframe) injectThemeIntoIframe(iframe); + }); + }, [themeVariables, themeClass]); + return ( +
+ { + components.map((component) => ( +
+

+ { component.name } +

+ +
+ { + component.stories.map((story, storyIndex) => ( +
+
+