diff --git a/.eslintignore b/.eslintignore index c783be4f4..5169dece6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,4 @@ CHANGELOG.md .yarn_home/ /test/integration/ /storybook-static/ - +!.storybook diff --git a/.storybook/DocsContainer.js b/.storybook/DocsContainer.tsx similarity index 57% rename from .storybook/DocsContainer.js rename to .storybook/DocsContainer.tsx index 868a401fb..d11948518 100644 --- a/.storybook/DocsContainer.js +++ b/.storybook/DocsContainer.tsx @@ -1,7 +1,10 @@ - -import React, { useEffect } from "react"; -import { DocsContainer as BaseContainer } from "@storybook/addon-docs"; -import { useDarkMode } from "storybook-dark-mode"; +import React, { PropsWithChildren, useEffect } from "react"; +import { + DocsContainer as BaseContainer, + DocsContainerProps, + Unstyled +} from "@storybook/addon-docs/blocks"; +import { useDarkMode } from "@vueless/storybook-dark-mode"; import { darkTheme, lightTheme } from "./customTheme"; import "../dist/dsfr/utility/icons/icons.min.css"; import "../dist/dsfr/dsfr.css"; @@ -15,16 +18,13 @@ startReactDsfr({ "useLang": () => "fr" }); -export const DocsContainer = ({ children, context }) => { +export const DocsContainer = ({ children, context }: PropsWithChildren) => { const isStorybookUiDark = useDarkMode(); const { setIsDark } = useIsDark(); - useEffect( - ()=> { - setIsDark(isStorybookUiDark); - }, - [isStorybookUiDark] - ); + useEffect(() => { + setIsDark(isStorybookUiDark); + }, [isStorybookUiDark]); const backgroundColor = fr.colors.decisions.background.default.grey.default; @@ -52,26 +52,9 @@ export const DocsContainer = ({ children, context }) => { } `} - { - const storyContext = context.storyById(id); - return { - ...storyContext, - "parameters": { - ...storyContext?.parameters, - "docs": { - ...storyContext?.parameters?.docs, - "theme": isStorybookUiDark ? darkTheme : lightTheme - } - } - }; - } - }} - > + - {children} + {children} diff --git a/.storybook/customTheme.js b/.storybook/customTheme.js deleted file mode 100644 index fac2e29a0..000000000 --- a/.storybook/customTheme.js +++ /dev/null @@ -1,35 +0,0 @@ -import { create } from "@storybook/theming"; - -const brandImage= "logo.png"; -const brandTitle= "@codegouvfr/react-dsfr"; -const brandUrl= "https://github.com/codegouvfr/react-dsfr"; -const fontBase= '"Marianne", arial, sans-serif'; -const fontCode= "monospace"; - -export const darkTheme = create({ - "base": "dark", - "appBg": "#1E1E1E", - "appContentBg": "#161616", - "barBg": "#161616", - "colorSecondary": "#8585F6", - "textColor": "#FFFFFF", - brandImage, - brandTitle, - brandUrl, - fontBase, - fontCode -}); - -export const lightTheme = create({ - "base": "light", - "appBg": "#F6F6F6", - "appContentBg": "#FFFFFF", - "barBg": "#FFFFFF", - "colorSecondary": "#000091", - "textColor": "#212121", - brandImage, - brandTitle, - brandUrl, - fontBase, - fontCode -}); diff --git a/.storybook/customTheme.ts b/.storybook/customTheme.ts new file mode 100644 index 000000000..54e08e63b --- /dev/null +++ b/.storybook/customTheme.ts @@ -0,0 +1,35 @@ +import { create } from "storybook/theming"; + +const brandImage = "logo.png"; +const brandTitle = "@codegouvfr/react-dsfr"; +const brandUrl = "https://github.com/codegouvfr/react-dsfr"; +const fontBase = '"Marianne", arial, sans-serif'; +const fontCode = "monospace"; + +export const darkTheme = create({ + base: "dark", + appBg: "#1E1E1E", + appContentBg: "#161616", + barBg: "#161616", + colorSecondary: "#8585F6", + textColor: "#FFFFFF", + brandImage, + brandTitle, + brandUrl, + fontBase, + fontCode +}); + +export const lightTheme = create({ + base: "light", + appBg: "#F6F6F6", + appContentBg: "#FFFFFF", + barBg: "#FFFFFF", + colorSecondary: "#000091", + textColor: "#212121", + brandImage, + brandTitle, + brandUrl, + fontBase, + fontCode +}); diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index 8ae8ed940..000000000 --- a/.storybook/main.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - "stories": [ - "../stories/*.stories.mdx", - "../stories/*.stories.@(ts|tsx)", - "../stories/blocks/*.stories.@(ts|tsx)", - "../stories/charts/*.stories.@(ts|tsx)", - "../stories/picto/*.stories.@(ts|tsx)", - ], - "addons": [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "storybook-dark-mode", - "@storybook/addon-a11y" - ], - "core": { - "builder": "webpack5" - }, - "staticDirs": ["../dist", "./static"] -}; diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 000000000..e524a205a --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,26 @@ +import { StorybookConfig } from "@storybook/react-vite"; + +export default { + framework: { + name: "@storybook/react-vite", + options: {} + }, + features: { + controls: true, + viewport: true, + backgrounds: false + }, + stories: [ + "../stories/*.mdx", + "../stories/*.stories.@(ts|tsx)", + "../stories/blocks/*.stories.@(ts|tsx)", + "../stories/charts/*.stories.@(ts|tsx)" + ], + addons: [ + "@vueless/storybook-dark-mode", + "@storybook/addon-links", + "@storybook/addon-a11y", + "@storybook/addon-docs" + ], + staticDirs: ["../dist", "./static"] +} satisfies StorybookConfig; diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html index 69d9dc1da..4c56c0f76 100644 --- a/.storybook/manager-head.html +++ b/.storybook/manager-head.html @@ -33,4 +33,24 @@ [data-parent-id^="hidden"] { display: none !important; } + + /* full manager loader (circle) */ + body.dark div[aria-label^="Content is loading..."] { + border-color: rgb(133, 133, 246) rgba(130, 130, 243, 0.29) rgba(130, 130, 243, 0.29) !important; + mix-blend-mode: normal !important; + } + + body:not(.dark) div[aria-label^="Content is loading..."] { + border-color: rgb(0, 0, 145) rgba(0, 0, 142, 0.29) rgba(0, 0, 142, 0.29) !important; + mix-blend-mode: normal !important; + } + + /* full manager page loader (dsfr vars not available) */ + body.dark section[aria-labelledby="main-preview-heading"] div:has(+ #storybook-preview-wrapper) { + background-color: #161616 !important; + } + + body:not(.dark) section[aria-labelledby="main-preview-heading"] div:has(+ #storybook-preview-wrapper) { + background-color: #ffffff !important; + } \ No newline at end of file diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 000000000..5d4845863 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,49 @@ + \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js deleted file mode 100644 index e1cbc3033..000000000 --- a/.storybook/preview.js +++ /dev/null @@ -1,128 +0,0 @@ -import { darkTheme, lightTheme } from "./customTheme"; -import { DocsContainer } from "./DocsContainer"; - -export const parameters = { - "actions": { "argTypesRegex": "^on[A-Z].*" }, - "controls": { - "matchers": { - "color": /(background|color)$/i, - "date": /Date$/, - }, - }, - "backgrounds": { "disable": true }, - "darkMode": { - "light": lightTheme, - "dark": darkTheme, - }, - "docs": { - "container": DocsContainer - }, - "viewport": { - "viewports": { - "1440p": { - "name": "1440p", - "styles": { - "width": "2560px", - "height": "1440px", - }, - }, - "fullHD": { - "name": "Full HD", - "styles": { - "width": "1920px", - "height": "1080px", - }, - }, - "macBookProBig": { - "name": "MacBook Pro Big", - "styles": { - "width": "1024px", - "height": "640px", - }, - }, - "macBookProMedium": { - "name": "MacBook Pro Medium", - "styles": { - "width": "1440px", - "height": "900px", - }, - }, - "macBookProSmall": { - "name": "MacBook Pro Small", - "styles": { - "width": "1680px", - "height": "1050px", - }, - }, - "pcAgent": { - "name": "PC Agent", - "styles": { - "width": "960px", - "height": "540px", - }, - }, - "iphone12Pro": { - "name": "Iphone 12 pro", - "styles": { - "width": "390px", - "height": "844px", - }, - }, - "iphone5se":{ - "name": "Iphone 5/SE", - "styles": { - "width": "320px", - "height": "568px", - }, - }, - "ipadPro": { - "name": "Ipad pro", - "styles": { - "width": "1240px", - "height": "1366px", - }, - }, - "Galaxy s9+": { - "name": "Galaxy S9+", - "styles": { - "width": "320px", - "height": "658px", - }, - } - }, - }, - "options": { - "storySort": (a, b) => - getHardCodedWeight(b[1].kind) - getHardCodedWeight(a[1].kind), - }, -}; - -const { getHardCodedWeight } = (() => { - - const orderedPagesPrefix = [ - "🇫🇷 Introduction", - //"components", - "components/Header", - "components/Footer", - "components/consentManagement", - "components/Alert", - "components/Tabs", - "components/Stepper", - "components/Button", - "components/FranceConnectButton", - "components/ProConnectButton" - ]; - - function getHardCodedWeight(kind) { - - for (let i = 0; i < orderedPagesPrefix.length; i++) { - if (kind.toLowerCase().startsWith(orderedPagesPrefix[i].toLowerCase())) { - return orderedPagesPrefix.length - i; - } - } - - return 0; - } - - return { getHardCodedWeight }; -})(); diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 000000000..0ee6827ad --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,120 @@ +import { darkTheme, lightTheme } from "./customTheme"; +import { DocsContainer } from "./DocsContainer"; +import { Preview } from "@storybook/react-vite"; + +const preview: Preview = { + tags: ["autodocs"], + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/ + } + }, + darkMode: { + light: lightTheme, + dark: darkTheme + }, + docs: { + container: DocsContainer + }, + viewport: { + options: { + "1440p": { + "name": "1440p", + "styles": { + "width": "2560px", + "height": "1440px" + } + }, + "fullHD": { + "name": "Full HD", + "styles": { + "width": "1920px", + "height": "1080px" + } + }, + "macBookProBig": { + "name": "MacBook Pro Big", + "styles": { + "width": "1024px", + "height": "640px" + } + }, + "macBookProMedium": { + "name": "MacBook Pro Medium", + "styles": { + "width": "1440px", + "height": "900px" + } + }, + "macBookProSmall": { + "name": "MacBook Pro Small", + "styles": { + "width": "1680px", + "height": "1050px" + } + }, + "pcAgent": { + "name": "PC Agent", + "styles": { + "width": "960px", + "height": "540px" + } + }, + "iphone12Pro": { + "name": "Iphone 12 pro", + "styles": { + "width": "390px", + "height": "844px" + } + }, + "iphone5se": { + "name": "Iphone 5/SE", + "styles": { + "width": "320px", + "height": "568px" + } + }, + "ipadPro": { + "name": "Ipad pro", + "styles": { + "width": "1240px", + "height": "1366px" + } + }, + "Galaxy s9+": { + "name": "Galaxy S9+", + "styles": { + "width": "320px", + "height": "658px" + } + } + } + }, + options: { + storySort: { + method: "alphabetical", + order: [ + "🇫🇷 Introduction", + "components", + [ + "Header", + "Footer", + "consentManagement", + "Alert", + "Tabs", + "Stepper", + "Button", + "FranceConnectButton", + "ProConnectButton", + "*" + ] + ] + } + } + } +}; + +export default preview; diff --git a/package.json b/package.json index 2bf9029ce..c3eee16ca 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "_format": "prettier '**/*.{ts,tsx,json,md}'", "format": "yarn _format --write", "format:check": "yarn _format --list-different", - "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", "prestorybook": "yarn build && node dist/bin/react-dsfr update-icons", "prebuild-storybook": "yarn prestorybook" }, @@ -83,15 +83,10 @@ "@gouvfr/dsfr-chart": "^1.0.0", "@mui/icons-material": "^5.14.18", "@mui/material": "^5.14.18", - "@storybook/addon-a11y": "^6.5.16", - "@storybook/addon-actions": "^6.5.13", - "@storybook/addon-essentials": "^6.5.13", - "@storybook/addon-interactions": "^6.5.13", - "@storybook/addon-links": "^6.5.13", - "@storybook/builder-webpack5": "^6.5.13", - "@storybook/manager-webpack5": "^6.5.13", - "@storybook/react": "^6.5.13", - "@storybook/testing-library": "^0.0.13", + "@storybook/addon-a11y": "10.1.11", + "@storybook/addon-docs": "10.1.11", + "@storybook/addon-links": "10.1.11", + "@storybook/react-vite": "^10.1.11", "@tanstack/react-virtual": "^3.0.0-beta.39", "@types/css": "^0.0.33", "@types/jsdom": "^21.1.7", @@ -100,14 +95,16 @@ "@types/node": "^18.7.18", "@types/react": "18.0.21", "@types/react-dom": "18.0.6", + "@types/yargs-parser": "^21.0.3", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", + "@vueless/storybook-dark-mode": "^10.0.4", "babel-loader": "^8.3.0", "chromatic": "^6.17.2", "css": "^3.0.0", - "eslint": "^7.26.0", + "eslint": "^8", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-storybook": "^0.6.7", + "eslint-plugin-storybook": "10.1.11", "evt": "^2.4.2", "fzf": "^0.5.1", "husky": "^4.3.8", @@ -123,12 +120,13 @@ "react": "18.2.0", "react-dom": "18.2.0", "remixicon": "^4.2.0", - "storybook-dark-mode": "^1.1.2", + "storybook": "10.1.11", "svgo": "^3.3.2", "ts-node": "^10.9.1", "tss-react": "^4.9.1", "type-route": "^1.0.1", "typescript": "^4.9.1", + "vite": "^7.0.0", "vitest": "^0.24.3" }, "main": "dist/fr/index.js", diff --git a/src/Table.tsx b/src/Table.tsx index 7a67b7a84..898bafa57 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -31,7 +31,17 @@ export namespace TableProps { type ExtractColorVariant = FrClassName extends `fr-table--${infer AccentColor}` ? Exclude< AccentColor, - "no-scroll" | "no-caption" | "caption-bottom" | "layout-fixed" | "bordered" + | "no-scroll" + | "no-caption" + | "caption-bottom" + | "layout-fixed" + | "bordered" + | "sm" + | "md" + | "lg" + | "xl" + | "xs" + | "multiline" > : never; diff --git a/stories/Accordion.stories.tsx b/stories/Accordion.stories.tsx index 3f867037b..4bc31786f 100644 --- a/stories/Accordion.stories.tsx +++ b/stories/Accordion.stories.tsx @@ -1,9 +1,7 @@ import { Accordion } from "../dist/Accordion"; import { getStoryFactory, logCallbacks } from "./getStory"; -import { sectionName } from "./sectionName"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { Accordion }, argTypes: { "label": { @@ -60,7 +58,7 @@ function ControlledAccordion() { "disabledProps": ["lang"] }); -export default meta; +export default { ...meta, title: "components/Accordion" }; export const Default = getStory({ "label": "Name of the Accordion", diff --git a/stories/AgentConnectButton.stories.tsx b/stories/AgentConnectButton.stories.tsx index 73ca74e27..49e23bb32 100644 --- a/stories/AgentConnectButton.stories.tsx +++ b/stories/AgentConnectButton.stories.tsx @@ -1,16 +1,14 @@ import { AgentConnectButton } from "../dist/AgentConnectButton"; -import { sectionName } from "./sectionName"; import { getStoryFactory, logCallbacks } from "./getStory"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { AgentConnectButton }, "description": ` - [See AgentConnect documentation](https://github.com/france-connect/Documentation-AgentConnect/blob/main/doc_fs/implementation_fca/bouton_fca.md) - [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/AgentConnectButton.tsx)` }); -export default meta; +export default { ...meta, title: "components/AgentConnectButton" }; export const Default = getStory({ "url": "https://example.com" diff --git a/stories/Alert.stories.tsx b/stories/Alert.stories.tsx index 0eb80fd58..a3bb5ea46 100644 --- a/stories/Alert.stories.tsx +++ b/stories/Alert.stories.tsx @@ -1,11 +1,10 @@ import { Alert, type AlertProps } from "../dist/Alert"; -import { sectionName } from "./sectionName"; + import { getStoryFactory, logCallbacks } from "./getStory"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { Alert }, "description": ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/alerte) @@ -78,13 +77,13 @@ const [ isClosed, setIsClosed ] = useState(false); this means that when the close button is clicked the \`onClose()\` callback will be called but you are responsible for setting \`isClosed\` to \`false\`, the \`\` wont close itself.`, - "control": { "type": null } + "control": false } }, "disabledProps": ["lang"] }); -export default meta; +export default { ...meta, title: "components/Alert" }; export const Default = getStory({ "severity": "success", diff --git a/stories/Badge.stories.tsx b/stories/Badge.stories.tsx index 724774c7d..010f5155f 100644 --- a/stories/Badge.stories.tsx +++ b/stories/Badge.stories.tsx @@ -1,11 +1,10 @@ import { Badge, type BadgeProps } from "../dist/Badge"; -import { sectionName } from "./sectionName"; + import { getStoryFactory } from "./getStory"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { Badge }, description: ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/badge) @@ -49,7 +48,7 @@ const { meta, getStory } = getStoryFactory({ "disabledProps": ["lang"] }); -export default meta; +export default { ...meta, title: "components/Badge" }; export const Default = getStory({ "severity": "success", diff --git a/stories/Breadcrumb.stories.tsx b/stories/Breadcrumb.stories.tsx index 51a812bc3..5f0d4c047 100644 --- a/stories/Breadcrumb.stories.tsx +++ b/stories/Breadcrumb.stories.tsx @@ -1,9 +1,8 @@ import { Breadcrumb } from "../dist/Breadcrumb"; -import { sectionName } from "./sectionName"; + import { getStoryFactory } from "./getStory"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { Breadcrumb }, "description": ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/fil-d-ariane) @@ -11,7 +10,7 @@ const { meta, getStory } = getStoryFactory({ "disabledProps": ["lang"] }); -export default meta; +export default { ...meta, title: "components/Breadcrumb" }; export const Default = getStory({ "homeLinkProps": { "href": "/" }, diff --git a/stories/Button.stories.tsx b/stories/Button.stories.tsx index eee756cf3..12e8561d7 100644 --- a/stories/Button.stories.tsx +++ b/stories/Button.stories.tsx @@ -1,11 +1,10 @@ import { Button, type ButtonProps } from "../dist/Button"; -import { sectionName } from "./sectionName"; + import { getStoryFactory, logCallbacks } from "./getStory"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { Button }, "description": ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bouton) @@ -64,17 +63,17 @@ const { meta, getStory } = getStoryFactory({ "nativeButtonProps": { "description": `Can be used to attach extra props to the underlying native button. Example: \`{ "aria-controls": "fr-modal-1", onMouseEnter: event => {...} }\``, - "control": { "type": null } + "control": false }, "children": { "description": "The label of the button", - "control": { "type": "string" } + "control": { "type": "text" } } }, "disabledProps": ["lang"] }); -export default meta; +export default { ...meta, title: "components/Button" }; export const Default = getStory({ "children": "Label button", diff --git a/stories/ButtonsGroup.stories.tsx b/stories/ButtonsGroup.stories.tsx index 2fd3be8a1..d77046180 100644 --- a/stories/ButtonsGroup.stories.tsx +++ b/stories/ButtonsGroup.stories.tsx @@ -1,11 +1,10 @@ import { ButtonsGroup, type ButtonsGroupProps } from "../dist/ButtonsGroup"; -import { sectionName } from "./sectionName"; + import { getStoryFactory } from "./getStory"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { ButtonsGroup }, "description": ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/groupe-de-boutons) @@ -89,14 +88,14 @@ const { meta, getStory } = getStoryFactory({ }, "buttons": { "description": `An array of ButtonProps (at least 1)`, - "control": { "type": null } + "control": false } }, "disabledProps": ["lang"], "defaultContainerWidth": 800 }); -export default meta; +export default { ...meta, title: "components/ButtonsGroup" }; export const Default = getStory({ "buttons": [ diff --git a/stories/CallOut.stories.tsx b/stories/CallOut.stories.tsx index 9732d3f98..5c23c2a87 100644 --- a/stories/CallOut.stories.tsx +++ b/stories/CallOut.stories.tsx @@ -1,11 +1,10 @@ import { CallOut, type CallOutProps } from "../dist/CallOut"; -import { sectionName } from "./sectionName"; + import { getStoryFactory, logCallbacks } from "./getStory"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; const { meta, getStory } = getStoryFactory({ - sectionName, "wrappedComponent": { CallOut }, "description": ` - [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/mise-en-avant) @@ -15,6 +14,10 @@ const { meta, getStory } = getStoryFactory({ "title": { "description": "Optional" }, + "children": { + "description": "Optional", + "control": { "type": "text" } + }, "colorVariant": { "options": (() => { const options = [ @@ -63,12 +66,12 @@ const { meta, getStory } = getStoryFactory({ "buttonProps": { "description": "The same props you would pass to a `