diff --git a/.pnp.cjs b/.pnp.cjs index ec6031abc..f0bff2fd0 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -214,6 +214,10 @@ const RAW_RUNTIME_STATE = "name": "@atls-ui/design",\ "reference": "workspace:ui/design"\ },\ + {\ + "name": "@atls-ui/design-storybook",\ + "reference": "workspace:ui/design-storybook"\ + },\ {\ "name": "@atls-ui/theme",\ "reference": "workspace:ui/theme"\ @@ -289,6 +293,7 @@ const RAW_RUNTIME_STATE = ["@atls-ui-parts/tooltip", ["workspace:ui-parts/tooltip"]],\ ["@atls-ui-parts/upload", ["virtual:8480592f2ac0c5b00b17bd33d76d0c6d79665a3657c712c5bc5180464a86ac9bcb81d64ef54341b94b31e43db66a2fad906cfc6036c573c170560d657b7f96aa#workspace:ui-parts/upload", "workspace:ui-parts/upload"]],\ ["@atls-ui/design", ["workspace:ui/design"]],\ + ["@atls-ui/design-storybook", ["virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook", "workspace:ui/design-storybook"]],\ ["@atls-ui/theme", ["workspace:ui/theme"]],\ ["@atls-utils/use-float", ["virtual:e8b379d238bee0c446fdeaddb9725553fcee91eeb8db9f1f4b47e4e36a5c6c26c07ae1861b7d0a5dd574863bc008f048d25fc793392ceeaade0c9801b921d418#workspace:utils/use-float", "workspace:utils/use-float"]],\ ["@atls-utils/use-hover", ["virtual:90c3fa37a29dbddd012ae896ef9d136c46ca1d320faf1baf430439ab5aac7e1e7c8ef5281a72424df8d016a8c33c9d5ffe66ee23d2face19f27de3042508900f#workspace:utils/use-hover", "virtual:ab74b0ad21d788b5faaef36e69cfdc113b850f1834ee613c0e84e69c90b4422225458e49681dd5efa10b62df03744b2c5390da900b6092fa9e40fd6605cda940#workspace:utils/use-hover", "workspace:utils/use-hover"]],\ @@ -417,6 +422,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./ui-admin/design/",\ "packageDependencies": [\ ["@atls-ui-admin/design", "workspace:ui-admin/design"],\ + ["@atls-ui/design-storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook"],\ ["@chromatic-com/storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.2.1"],\ ["@mdx-js/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.1.1"],\ ["@storybook/addon-docs", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ @@ -1281,6 +1287,7 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@atls-ui-parts/design", "workspace:ui-parts/design"],\ ["@atls-ui-parts/theme", "virtual:6e3d13d4bb141546c851eafb803659d9bef8a8a4b826ab761e48399dcbf8cda80227a38097da69531ddae63c1380c43a5fad649d9ff8e485d2bc4c96720b9bd0#workspace:ui-parts/theme"],\ + ["@atls-ui/design-storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook"],\ ["@chromatic-com/storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.2.1"],\ ["@mdx-js/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.1.1"],\ ["@storybook/addon-docs", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ @@ -2149,6 +2156,7 @@ const RAW_RUNTIME_STATE = ["@atls-ui-parts/text", "virtual:5dae0025774218e7d5b25f9293624b65d07c18a455c0543b6b0cbcc49eab1f8daaf93f688033cd0934a58e64a6421749d3e30955e506415c50f4612545e60177#workspace:ui-parts/text"],\ ["@atls-ui-parts/theme", "virtual:6e3d13d4bb141546c851eafb803659d9bef8a8a4b826ab761e48399dcbf8cda80227a38097da69531ddae63c1380c43a5fad649d9ff8e485d2bc4c96720b9bd0#workspace:ui-parts/theme"],\ ["@atls-ui/design", "workspace:ui/design"],\ + ["@atls-ui/design-storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook"],\ ["@chromatic-com/storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.2.1"],\ ["@mdx-js/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.1.1"],\ ["@storybook/addon-docs", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ @@ -2178,6 +2186,78 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@atls-ui/design-storybook", [\ + ["virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook", {\ + "packageLocation": "./.yarn/__virtual__/@atls-ui-design-storybook-virtual-f1941507bc/1/ui/design-storybook/",\ + "packageDependencies": [\ + ["@atls-ui-parts/theme", "virtual:6e3d13d4bb141546c851eafb803659d9bef8a8a4b826ab761e48399dcbf8cda80227a38097da69531ddae63c1380c43a5fad649d9ff8e485d2bc4c96720b9bd0#workspace:ui-parts/theme"],\ + ["@atls-ui/design-storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#workspace:ui/design-storybook"],\ + ["@chromatic-com/storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.2.1"],\ + ["@mdx-js/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.1.1"],\ + ["@storybook/addon-docs", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["@storybook/addon-links", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:10.4.6"],\ + ["@storybook/addon-styling-webpack", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.0.2"],\ + ["@storybook/addon-webpack5-compiler-swc", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:4.0.3"],\ + ["@storybook/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:10.4.6"],\ + ["@storybook/react-webpack5", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:10.4.6"],\ + ["@types/node", "npm:25.9.3"],\ + ["@types/react", "npm:19.1.2"],\ + ["@types/react-dom", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:19.1.2"],\ + ["@types/vanilla-extract__css", null],\ + ["@types/vanilla-extract__dynamic", null],\ + ["@vanilla-extract/css", "npm:1.17.1"],\ + ["@vanilla-extract/dynamic", "npm:2.1.2"],\ + ["@vanilla-extract/webpack-plugin", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:2.3.27"],\ + ["css-loader", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:7.1.4"],\ + ["mini-css-extract-plugin", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:2.10.2"],\ + ["react", "npm:19.1.0"],\ + ["react-dom", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:19.1.0"],\ + ["storybook", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["style-loader", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:4.0.0"],\ + ["typescript", "patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07"],\ + ["webpack", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.107.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/vanilla-extract__css",\ + "@types/vanilla-extract__dynamic",\ + "@vanilla-extract/css",\ + "@vanilla-extract/dynamic",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "SOFT"\ + }],\ + ["workspace:ui/design-storybook", {\ + "packageLocation": "./ui/design-storybook/",\ + "packageDependencies": [\ + ["@atls-ui-parts/theme", "virtual:6e3d13d4bb141546c851eafb803659d9bef8a8a4b826ab761e48399dcbf8cda80227a38097da69531ddae63c1380c43a5fad649d9ff8e485d2bc4c96720b9bd0#workspace:ui-parts/theme"],\ + ["@atls-ui/design-storybook", "workspace:ui/design-storybook"],\ + ["@chromatic-com/storybook", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.2.1"],\ + ["@mdx-js/react", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.1.1"],\ + ["@storybook/addon-docs", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["@storybook/addon-links", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:10.4.6"],\ + ["@storybook/addon-styling-webpack", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:3.0.2"],\ + ["@storybook/addon-webpack5-compiler-swc", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:4.0.3"],\ + ["@storybook/react", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["@storybook/react-webpack5", "virtual:cdaa5ab34500e184b53a303b520bfb39dcbf439bcc1b4094426fba3b375978e4de3c486df56031e13f8bd9371bf94c96e9b01049740f715ea1498362cb896d56#npm:10.4.6"],\ + ["@types/node", "npm:25.9.3"],\ + ["@types/react", "npm:19.1.2"],\ + ["@vanilla-extract/css", "npm:1.17.1"],\ + ["@vanilla-extract/dynamic", "npm:2.1.2"],\ + ["@vanilla-extract/webpack-plugin", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:2.3.27"],\ + ["css-loader", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:7.1.4"],\ + ["mini-css-extract-plugin", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:2.10.2"],\ + ["react", "npm:19.1.0"],\ + ["react-dom", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:19.1.0"],\ + ["storybook", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["style-loader", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:4.0.0"],\ + ["typescript", "patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07"],\ + ["webpack", "virtual:5d5a2d83c74dbbb6a29a65ad5d9fce09aed4258bbc7c7bef7d0ae41fe2cbed3efa30eb23487731e7ba540c2485008f2e2cdc8aa4b98cd0d0d6df800a358cdf39#npm:5.107.2"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@atls-ui/theme", [\ ["workspace:ui/theme", {\ "packageLocation": "./ui/theme/",\ @@ -5721,6 +5801,41 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["virtual:6a0ec2204e77a9a2f003f51c4891fd1bf4f36d163b45d00b1a1ce8ef2841b4ea224a725c500fbc50858a6de9dab3aeb504bf12d51138889acc6b0ad772602013#npm:10.4.6", {\ + "packageLocation": "./.yarn/__virtual__/@storybook-preset-react-webpack-virtual-409ee9ed01/2/.yarn/berry/cache/@storybook-preset-react-webpack-npm-10.4.6-f93d904834-10.zip/node_modules/@storybook/preset-react-webpack/",\ + "packageDependencies": [\ + ["@storybook/core-webpack", "virtual:1209134d7671defbde3564a5da13e4aad45a628455a291881e4d3aaa39cc33a130df5f72653173dd8062351221c1546ec99648e00da188d28a064f354453327b#npm:10.4.6"],\ + ["@storybook/preset-react-webpack", "virtual:6a0ec2204e77a9a2f003f51c4891fd1bf4f36d163b45d00b1a1ce8ef2841b4ea224a725c500fbc50858a6de9dab3aeb504bf12d51138889acc6b0ad772602013#npm:10.4.6"],\ + ["@storybook/react-docgen-typescript-plugin", "virtual:9d3d6b2c6ffe8652a224024157e7ebc3b10c2e296747c0d607dd006f31024b81d0da1d0f4b2458250d961bfc4a6f54b78f422c22ad0bfa400720825d96632675#npm:1.0.6--canary.9.0c3f3b7.0"],\ + ["@types/react", "npm:19.1.2"],\ + ["@types/react-dom", null],\ + ["@types/semver", "npm:7.7.1"],\ + ["@types/storybook", null],\ + ["@types/typescript", null],\ + ["file-system-cache", "npm:2.3.0"],\ + ["magic-string", "npm:0.30.11"],\ + ["react", "npm:19.1.0"],\ + ["react-docgen", "npm:7.1.1"],\ + ["react-dom", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:19.1.0"],\ + ["resolve", "patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d"],\ + ["semver", "npm:7.8.4"],\ + ["storybook", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["tsconfig-paths", "npm:4.2.0"],\ + ["typescript", "patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07"],\ + ["webpack", "virtual:1209134d7671defbde3564a5da13e4aad45a628455a291881e4d3aaa39cc33a130df5f72653173dd8062351221c1546ec99648e00da188d28a064f354453327b#npm:5.93.0"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "@types/storybook",\ + "@types/typescript",\ + "react-dom",\ + "react",\ + "storybook",\ + "typescript"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:992408f811041cc0e95ba8504b4fb0173c03b1042a53ddef65c336b1acc83b552c91f4a979363d3a9290a7fa9d93c585f94a6047b9dfdc91ced2255b384bbade#npm:10.4.6", {\ "packageLocation": "./.yarn/__virtual__/@storybook-preset-react-webpack-virtual-9d3d6b2c6f/2/.yarn/berry/cache/@storybook-preset-react-webpack-npm-10.4.6-f93d904834-10.zip/node_modules/@storybook/preset-react-webpack/",\ "packageDependencies": [\ @@ -5943,6 +6058,34 @@ const RAW_RUNTIME_STATE = "typescript"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:cdaa5ab34500e184b53a303b520bfb39dcbf439bcc1b4094426fba3b375978e4de3c486df56031e13f8bd9371bf94c96e9b01049740f715ea1498362cb896d56#npm:10.4.6", {\ + "packageLocation": "./.yarn/__virtual__/@storybook-react-webpack5-virtual-6a0ec2204e/2/.yarn/berry/cache/@storybook-react-webpack5-npm-10.4.6-b4ac1f7785-10.zip/node_modules/@storybook/react-webpack5/",\ + "packageDependencies": [\ + ["@storybook/builder-webpack5", "virtual:992408f811041cc0e95ba8504b4fb0173c03b1042a53ddef65c336b1acc83b552c91f4a979363d3a9290a7fa9d93c585f94a6047b9dfdc91ced2255b384bbade#npm:10.4.6"],\ + ["@storybook/preset-react-webpack", "virtual:6a0ec2204e77a9a2f003f51c4891fd1bf4f36d163b45d00b1a1ce8ef2841b4ea224a725c500fbc50858a6de9dab3aeb504bf12d51138889acc6b0ad772602013#npm:10.4.6"],\ + ["@storybook/react", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["@storybook/react-webpack5", "virtual:cdaa5ab34500e184b53a303b520bfb39dcbf439bcc1b4094426fba3b375978e4de3c486df56031e13f8bd9371bf94c96e9b01049740f715ea1498362cb896d56#npm:10.4.6"],\ + ["@types/react", "npm:19.1.2"],\ + ["@types/react-dom", null],\ + ["@types/storybook", null],\ + ["@types/typescript", null],\ + ["react", "npm:19.1.0"],\ + ["react-dom", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:19.1.0"],\ + ["storybook", "virtual:76ebc481ac02aa95961a7136f7e551f7070fbf8eb171c0ca48399e504fe0f0f0edaf1d4f5953b763ae0b309e0e91ebf7d54e047cc0a0270d8aee71a756c50a70#npm:10.4.6"],\ + ["typescript", "patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "@types/storybook",\ + "@types/typescript",\ + "react-dom",\ + "react",\ + "storybook",\ + "typescript"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@svgr/babel-plugin-add-jsx-attribute", [\ diff --git a/ui-admin/design/package.json b/ui-admin/design/package.json index d2509044d..8d78281f0 100644 --- a/ui-admin/design/package.json +++ b/ui-admin/design/package.json @@ -12,6 +12,7 @@ "start-image": "node dist/index.js" }, "devDependencies": { + "@atls-ui/design-storybook": "workspace:*", "@chromatic-com/storybook": "5.2.1", "@mdx-js/react": "3.1.1", "@storybook/addon-docs": "10.4.6", diff --git a/ui-admin/design/src/constants.ts b/ui-admin/design/src/constants.ts new file mode 100644 index 000000000..b555c5b24 --- /dev/null +++ b/ui-admin/design/src/constants.ts @@ -0,0 +1,6 @@ +export const designStorybookStories = [ + '../../**/src/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', + '../../**/src/*.mdx', + '../../**/stories/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', + '../../**/stories/*.mdx', +] diff --git a/ui-admin/design/src/main.ts b/ui-admin/design/src/main.ts index 6df24ce82..4bb863919 100644 --- a/ui-admin/design/src/main.ts +++ b/ui-admin/design/src/main.ts @@ -1,84 +1,7 @@ -import type { StorybookConfig } from '@storybook/react-webpack5' +import { createDesignStorybookMain } from '@atls-ui/design-storybook/main' -import { createRequire } from 'node:module' -import { dirname } from 'node:path' -import { join } from 'node:path' +import { designStorybookStories } from './constants.js' -import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin' -import MiniCssExtractPlugin from 'mini-css-extract-plugin' - -const require = createRequire(import.meta.url) - -const getAbsolutePath = (value: string): string => - dirname(require.resolve(join(value, 'package.json'))) - -const config: StorybookConfig = { - stories: [ - '../../**/src/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', - '../../**/src/*.mdx', - '../../**/stories/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', - '../../**/stories/*.mdx', - ], - addons: [ - getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), - getAbsolutePath('@storybook/addon-links'), - getAbsolutePath('@storybook/addon-docs'), - getAbsolutePath('@chromatic-com/storybook'), - { - name: getAbsolutePath('@storybook/addon-styling-webpack'), - options: { - plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()], - rules: [ - { - test: /\.css$/, - sideEffects: true, - use: [ - require.resolve('style-loader'), - { - loader: require.resolve('css-loader'), - options: {}, - }, - ], - exclude: /\.vanilla\.css$/, - }, - { - test: /\.vanilla\.css$/i, - sideEffects: true, - use: [ - MiniCssExtractPlugin.loader, - { - loader: require.resolve('css-loader'), - options: { url: false }, - }, - ], - }, - ], - }, - }, - ], - framework: { - name: getAbsolutePath('@storybook/react-webpack5'), - options: {}, - }, - webpackFinal: async (webpackConfig) => { - webpackConfig.resolve = { - ...webpackConfig.resolve, - extensionAlias: { - ...webpackConfig.resolve?.extensionAlias, - '.js': ['.ts', '.tsx', '.js'], - }, - } - - if (webpackConfig.resolve.fallback) { - webpackConfig.resolve.fallback = { - ...webpackConfig.resolve.fallback, - assert: false, - url: false, - } - } - - return webpackConfig - }, -} - -export default config +export default createDesignStorybookMain({ + stories: designStorybookStories, +}) diff --git a/ui-admin/design/src/preview.tsx b/ui-admin/design/src/preview.tsx index 259534206..69ca60df6 100644 --- a/ui-admin/design/src/preview.tsx +++ b/ui-admin/design/src/preview.tsx @@ -1,14 +1,3 @@ -import type { Preview } from '@storybook/react' +import { createDesignStorybookPreview } from '@atls-ui/design-storybook/preview' -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -} - -export default preview +export default createDesignStorybookPreview() diff --git a/ui-parts/design/package.json b/ui-parts/design/package.json index 1895dd1e4..878aa5219 100644 --- a/ui-parts/design/package.json +++ b/ui-parts/design/package.json @@ -13,6 +13,7 @@ }, "devDependencies": { "@atls-ui-parts/theme": "workspace:*", + "@atls-ui/design-storybook": "workspace:*", "@chromatic-com/storybook": "5.2.1", "@mdx-js/react": "3.1.1", "@storybook/addon-docs": "10.4.6", diff --git a/ui-parts/design/src/constants.ts b/ui-parts/design/src/constants.ts new file mode 100644 index 000000000..b555c5b24 --- /dev/null +++ b/ui-parts/design/src/constants.ts @@ -0,0 +1,6 @@ +export const designStorybookStories = [ + '../../**/src/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', + '../../**/src/*.mdx', + '../../**/stories/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', + '../../**/stories/*.mdx', +] diff --git a/ui-parts/design/src/main.ts b/ui-parts/design/src/main.ts index 6df24ce82..4bb863919 100644 --- a/ui-parts/design/src/main.ts +++ b/ui-parts/design/src/main.ts @@ -1,84 +1,7 @@ -import type { StorybookConfig } from '@storybook/react-webpack5' +import { createDesignStorybookMain } from '@atls-ui/design-storybook/main' -import { createRequire } from 'node:module' -import { dirname } from 'node:path' -import { join } from 'node:path' +import { designStorybookStories } from './constants.js' -import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin' -import MiniCssExtractPlugin from 'mini-css-extract-plugin' - -const require = createRequire(import.meta.url) - -const getAbsolutePath = (value: string): string => - dirname(require.resolve(join(value, 'package.json'))) - -const config: StorybookConfig = { - stories: [ - '../../**/src/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', - '../../**/src/*.mdx', - '../../**/stories/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', - '../../**/stories/*.mdx', - ], - addons: [ - getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), - getAbsolutePath('@storybook/addon-links'), - getAbsolutePath('@storybook/addon-docs'), - getAbsolutePath('@chromatic-com/storybook'), - { - name: getAbsolutePath('@storybook/addon-styling-webpack'), - options: { - plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()], - rules: [ - { - test: /\.css$/, - sideEffects: true, - use: [ - require.resolve('style-loader'), - { - loader: require.resolve('css-loader'), - options: {}, - }, - ], - exclude: /\.vanilla\.css$/, - }, - { - test: /\.vanilla\.css$/i, - sideEffects: true, - use: [ - MiniCssExtractPlugin.loader, - { - loader: require.resolve('css-loader'), - options: { url: false }, - }, - ], - }, - ], - }, - }, - ], - framework: { - name: getAbsolutePath('@storybook/react-webpack5'), - options: {}, - }, - webpackFinal: async (webpackConfig) => { - webpackConfig.resolve = { - ...webpackConfig.resolve, - extensionAlias: { - ...webpackConfig.resolve?.extensionAlias, - '.js': ['.ts', '.tsx', '.js'], - }, - } - - if (webpackConfig.resolve.fallback) { - webpackConfig.resolve.fallback = { - ...webpackConfig.resolve.fallback, - assert: false, - url: false, - } - } - - return webpackConfig - }, -} - -export default config +export default createDesignStorybookMain({ + stories: designStorybookStories, +}) diff --git a/ui-parts/design/src/preview.tsx b/ui-parts/design/src/preview.tsx index a14f7f7f5..73ecdbca5 100644 --- a/ui-parts/design/src/preview.tsx +++ b/ui-parts/design/src/preview.tsx @@ -1,10 +1,10 @@ -import type { Preview } from '@storybook/react' -import type { ReactNode } from 'react' -import type { PropsWithChildren } from 'react' +import type { ReactNode } from 'react' +import type { PropsWithChildren } from 'react' -import { useEffect } from 'react' +import { useEffect } from 'react' -import { globalThemeId } from '@atls-ui-parts/theme' +import { globalThemeId } from '@atls-ui-parts/theme' +import { createDesignStorybookPreview } from '@atls-ui/design-storybook/preview' const ThemeContainer = ({ children }: PropsWithChildren): ReactNode => { useEffect(() => { @@ -14,20 +14,12 @@ const ThemeContainer = ({ children }: PropsWithChildren): ReactNode => { return children } -const preview: Preview = { - decorators: (Story) => ( - - - - ), - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -} - -export default preview +export default createDesignStorybookPreview({ + decorators: [ + (Story) => ( + + + + ), + ], +}) diff --git a/ui/design-storybook/package.json b/ui/design-storybook/package.json new file mode 100644 index 000000000..91987d784 --- /dev/null +++ b/ui/design-storybook/package.json @@ -0,0 +1,83 @@ +{ + "name": "@atls-ui/design-storybook", + "version": "0.1.1", + "private": true, + "license": "BSD-3-Clause", + "type": "module", + "exports": { + "./package.json": "./package.json", + "./config": "./src/config/index.ts", + "./main": "./src/storybook/main/index.ts", + "./preview": "./src/storybook/preview/index.ts", + "./ui/matrix": "./src/ui/matrix/index.ts" + }, + "bin": { + "hyperion-design-storybook": "./dist/app/cli.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rm -rf dist && tsc --project tsconfig.json", + "prepack": "yarn run build", + "postpack": "rm -rf dist" + }, + "dependencies": { + "@atls-ui-parts/theme": "workspace:*", + "@chromatic-com/storybook": "5.2.1", + "@mdx-js/react": "3.1.1", + "@storybook/addon-docs": "10.4.6", + "@storybook/addon-links": "10.4.6", + "@storybook/addon-styling-webpack": "3.0.2", + "@storybook/addon-webpack5-compiler-swc": "4.0.3", + "@storybook/react": "10.4.6", + "@storybook/react-webpack5": "10.4.6", + "@vanilla-extract/webpack-plugin": "2.3.27", + "css-loader": "7.1.4", + "mini-css-extract-plugin": "2.10.2", + "storybook": "10.4.6", + "style-loader": "4.0.0", + "webpack": "5.107.2" + }, + "devDependencies": { + "@types/node": "25.9.3", + "@types/react": "19.1.2", + "@vanilla-extract/css": "1.17.1", + "@vanilla-extract/dynamic": "2.1.2", + "react": "19.1.0", + "react-dom": "19.1.0", + "typescript": "5.5.4" + }, + "peerDependencies": { + "@vanilla-extract/css": "*", + "@vanilla-extract/dynamic": "*", + "react": "*", + "react-dom": "*" + }, + "publishConfig": { + "exports": { + "./package.json": "./package.json", + "./config": { + "import": "./dist/config/index.js", + "types": "./dist/config/index.d.ts", + "default": "./dist/config/index.js" + }, + "./main": { + "import": "./dist/storybook/main/index.js", + "types": "./dist/storybook/main/index.d.ts", + "default": "./dist/storybook/main/index.js" + }, + "./preview": { + "import": "./dist/storybook/preview/index.js", + "types": "./dist/storybook/preview/index.d.ts", + "default": "./dist/storybook/preview/index.js" + }, + "./ui/matrix": { + "import": "./dist/ui/matrix/index.js", + "types": "./dist/ui/matrix/index.d.ts", + "default": "./dist/ui/matrix/index.js" + } + } + }, + "typecheckSkipLibCheck": true +} diff --git a/ui/design-storybook/src/app/argv.ts b/ui/design-storybook/src/app/argv.ts new file mode 100644 index 000000000..9ffd951e5 --- /dev/null +++ b/ui/design-storybook/src/app/argv.ts @@ -0,0 +1,30 @@ +import type { CliOptions } from './interfaces.js' +import type { DesignStorybookCommand } from './interfaces.js' + +import { cliOptionNames } from './constants.js' +import { defaultCliOptions } from './constants.js' +import { designStorybookCommands } from './constants.js' + +const getOptionValue = (args: Array, name: string, fallback: string): string => { + const index = args.indexOf(name) + + if (index === -1) { + return fallback + } + + return args[index + 1] ?? fallback +} + +export const parseArgv = (args: Array): CliOptions => { + const command = designStorybookCommands.includes(args[0] as DesignStorybookCommand) + ? (args[0] as DesignStorybookCommand) + : defaultCliOptions.command + + return { + command, + configDir: getOptionValue(args, cliOptionNames.configDir, defaultCliOptions.configDir), + configPath: getOptionValue(args, cliOptionNames.configPath, defaultCliOptions.configPath), + outputDir: getOptionValue(args, cliOptionNames.outputDir, defaultCliOptions.outputDir), + port: getOptionValue(args, cliOptionNames.port, defaultCliOptions.port), + } +} diff --git a/ui/design-storybook/src/app/cli.ts b/ui/design-storybook/src/app/cli.ts new file mode 100644 index 000000000..3f18079a6 --- /dev/null +++ b/ui/design-storybook/src/app/cli.ts @@ -0,0 +1,11 @@ +import { runDesignStorybook } from './run.js' + +runDesignStorybook(process.argv.slice(2)).catch((error: unknown) => { + if (error instanceof Error) { + process.stderr.write(`${error.message}\n`) + } else { + process.stderr.write(`${String(error)}\n`) + } + + process.exitCode = 1 +}) diff --git a/ui/design-storybook/src/app/constants.ts b/ui/design-storybook/src/app/constants.ts new file mode 100644 index 000000000..963663ee1 --- /dev/null +++ b/ui/design-storybook/src/app/constants.ts @@ -0,0 +1,30 @@ +import type { CliOptions } from './interfaces.js' +import type { DesignStorybookCommand } from './interfaces.js' + +import { storybookBuildCommand } from '../storybook/runtime/constants.js' +import { storybookDevCommand } from '../storybook/runtime/constants.js' + +export const buildCommand = storybookBuildCommand +export const devCommand = storybookDevCommand +export const prepareCommand = 'prepare' + +export const designStorybookCommands: Array = [ + buildCommand, + devCommand, + prepareCommand, +] + +export const cliOptionNames = { + configDir: '--config-dir', + configPath: '--config', + outputDir: '--output-dir', + port: '--port', +} + +export const defaultCliOptions: CliOptions = { + command: devCommand, + configDir: '.hyperion/design-storybook', + configPath: 'design.storybook.config.mjs', + outputDir: 'dist', + port: '6006', +} diff --git a/ui/design-storybook/src/app/interfaces.ts b/ui/design-storybook/src/app/interfaces.ts new file mode 100644 index 000000000..cd48ff58c --- /dev/null +++ b/ui/design-storybook/src/app/interfaces.ts @@ -0,0 +1,9 @@ +export type DesignStorybookCommand = 'build' | 'dev' | 'prepare' + +export interface CliOptions { + command: DesignStorybookCommand + configDir: string + configPath: string + outputDir: string + port: string +} diff --git a/ui/design-storybook/src/app/run.ts b/ui/design-storybook/src/app/run.ts new file mode 100644 index 000000000..4fca4ce1c --- /dev/null +++ b/ui/design-storybook/src/app/run.ts @@ -0,0 +1,29 @@ +import { assertDesignStorybookConfig } from '../config/assert.js' +import { loadModule } from '../lib/module-loader/load.js' +import { writeStorybookFiles } from '../storybook/project/files.js' +import { runStorybook } from '../storybook/runtime/run.js' +import { parseArgv } from './argv.js' +import { prepareCommand } from './constants.js' + +export const runDesignStorybook = async (args: Array): Promise => { + const options = parseArgv(args) + const loadedModule = await loadModule(options.configPath) + assertDesignStorybookConfig(loadedModule.value) + + const loadedConfig = { + config: loadedModule.value, + path: loadedModule.path, + } + const configDir = await writeStorybookFiles(loadedConfig, options) + + if (options.command === prepareCommand) { + return + } + + await runStorybook({ + command: options.command, + configDir, + outputDir: options.outputDir, + port: options.port, + }) +} diff --git a/ui/design-storybook/src/config/assert.ts b/ui/design-storybook/src/config/assert.ts new file mode 100644 index 000000000..42532eee7 --- /dev/null +++ b/ui/design-storybook/src/config/assert.ts @@ -0,0 +1,71 @@ +import type { DesignStorybookConfig } from './interfaces.js' + +import { designStorybookConfigDefaultExportError } from './errors.js' +import { designStorybookConfigImportSpecifierError } from './errors.js' +import { designStorybookConfigMatrixComponentsError } from './errors.js' +import { designStorybookConfigMatrixError } from './errors.js' +import { designStorybookConfigMatrixItemError } from './errors.js' +import { designStorybookConfigStoriesError } from './errors.js' +import { designStorybookConfigStoriesValueError } from './errors.js' +import { designStorybookConfigStyleEntrypointsError } from './errors.js' +import { isImportSpecifier } from './guards.js' +import { isRecord } from './guards.js' +import { isStories } from './guards.js' +import { isStringArray } from './guards.js' + +const assertImportSpecifier = (value: unknown): void => { + if (!isImportSpecifier(value)) { + throw new Error(designStorybookConfigImportSpecifierError) + } +} + +const assertMatrixItem = (value: unknown): void => { + if (!isRecord(value) || typeof value.name !== 'string') { + throw new Error(designStorybookConfigMatrixItemError) + } + + assertImportSpecifier(value.component) + assertImportSpecifier(value.variants) +} + +const assertMatrixConfig = (value: unknown): void => { + if (value === undefined) { + return + } + + if (!isRecord(value)) { + throw new Error(designStorybookConfigMatrixError) + } + + if (!Array.isArray(value.components)) { + throw new Error(designStorybookConfigMatrixComponentsError) + } + + value.components.forEach(assertMatrixItem) + + if (value.title !== undefined && typeof value.title !== 'string') { + throw new Error(designStorybookConfigMatrixError) + } +} + +export function assertDesignStorybookConfig( + value: unknown +): asserts value is DesignStorybookConfig { + if (!isRecord(value)) { + throw new Error(designStorybookConfigDefaultExportError) + } + + if (!('stories' in value)) { + throw new Error(designStorybookConfigStoriesError) + } + + if (!isStories(value.stories)) { + throw new Error(designStorybookConfigStoriesValueError) + } + + if (value.styleEntrypoints !== undefined && !isStringArray(value.styleEntrypoints)) { + throw new Error(designStorybookConfigStyleEntrypointsError) + } + + assertMatrixConfig(value.matrix) +} diff --git a/ui/design-storybook/src/config/errors.ts b/ui/design-storybook/src/config/errors.ts new file mode 100644 index 000000000..4fce9816b --- /dev/null +++ b/ui/design-storybook/src/config/errors.ts @@ -0,0 +1,12 @@ +export const designStorybookConfigDefaultExportError = + 'design_storybook_config_default_export_required' +export const designStorybookConfigStoriesError = 'design_storybook_config_stories_required' +export const designStorybookConfigStoriesValueError = 'design_storybook_config_stories_invalid' +export const designStorybookConfigStyleEntrypointsError = + 'design_storybook_config_style_entrypoints_invalid' +export const designStorybookConfigMatrixError = 'design_storybook_config_matrix_invalid' +export const designStorybookConfigMatrixComponentsError = + 'design_storybook_config_matrix_components_invalid' +export const designStorybookConfigMatrixItemError = 'design_storybook_config_matrix_item_invalid' +export const designStorybookConfigImportSpecifierError = + 'design_storybook_config_import_specifier_invalid' diff --git a/ui/design-storybook/src/config/guards.ts b/ui/design-storybook/src/config/guards.ts new file mode 100644 index 000000000..2a448abbd --- /dev/null +++ b/ui/design-storybook/src/config/guards.ts @@ -0,0 +1,20 @@ +export const isRecord = (value: unknown): value is Record => + Boolean(value) && typeof value === 'object' && !Array.isArray(value) + +export const isStringArray = (value: unknown): value is Array => + Array.isArray(value) && value.every((item) => typeof item === 'string') + +export const isImportSpecifier = (value: unknown): boolean => + isRecord(value) && typeof value.module === 'string' && typeof value.exportName === 'string' + +const isStoriesSpecifier = (value: unknown): boolean => + isRecord(value) && + typeof value.directory === 'string' && + (value.files === undefined || typeof value.files === 'string') && + (value.titlePrefix === undefined || typeof value.titlePrefix === 'string') + +const isStoriesEntry = (value: unknown): boolean => + typeof value === 'string' || isStoriesSpecifier(value) + +export const isStories = (value: unknown): boolean => + typeof value === 'string' || (Array.isArray(value) && value.every(isStoriesEntry)) diff --git a/ui/design-storybook/src/config/index.ts b/ui/design-storybook/src/config/index.ts new file mode 100644 index 000000000..9b7fd4cce --- /dev/null +++ b/ui/design-storybook/src/config/index.ts @@ -0,0 +1 @@ +export type * from './interfaces.js' diff --git a/ui/design-storybook/src/config/interfaces.ts b/ui/design-storybook/src/config/interfaces.ts new file mode 100644 index 000000000..f74086b7b --- /dev/null +++ b/ui/design-storybook/src/config/interfaces.ts @@ -0,0 +1,29 @@ +import type { Preview } from '@storybook/react' +import type { StorybookConfig } from '@storybook/react-webpack5' + +export interface DesignImportSpecifier { + exportName: string + module: string +} + +export interface MatrixItemConfig { + component: DesignImportSpecifier + name: string + variants: DesignImportSpecifier +} + +export interface MatrixConfig { + components: Array + title?: string +} + +export interface DesignStorybookConfig { + addons?: NonNullable + decorators?: Preview['decorators'] + matrix?: MatrixConfig + parameters?: Preview['parameters'] + refs?: StorybookConfig['refs'] + stories: NonNullable + styleEntrypoints?: Array + webpackFinal?: StorybookConfig['webpackFinal'] +} diff --git a/ui/design-storybook/src/lib/module-loader/absolute-file-path.ts b/ui/design-storybook/src/lib/module-loader/absolute-file-path.ts new file mode 100644 index 000000000..fb9f305f5 --- /dev/null +++ b/ui/design-storybook/src/lib/module-loader/absolute-file-path.ts @@ -0,0 +1,5 @@ +import { isAbsolute } from 'node:path' +import { resolve } from 'node:path' + +export const toAbsoluteFilePath = (path: string): string => + isAbsolute(path) ? path : resolve(process.cwd(), path) diff --git a/ui/design-storybook/src/lib/module-loader/errors.ts b/ui/design-storybook/src/lib/module-loader/errors.ts new file mode 100644 index 000000000..cf7b8734f --- /dev/null +++ b/ui/design-storybook/src/lib/module-loader/errors.ts @@ -0,0 +1 @@ +export const missingDefaultExportError = 'module_default_export_required' diff --git a/ui/design-storybook/src/lib/module-loader/interfaces.ts b/ui/design-storybook/src/lib/module-loader/interfaces.ts new file mode 100644 index 000000000..64b1e0d06 --- /dev/null +++ b/ui/design-storybook/src/lib/module-loader/interfaces.ts @@ -0,0 +1,4 @@ +export interface LoadedModule { + path: string + value: unknown +} diff --git a/ui/design-storybook/src/lib/module-loader/load.ts b/ui/design-storybook/src/lib/module-loader/load.ts new file mode 100644 index 000000000..ae0321342 --- /dev/null +++ b/ui/design-storybook/src/lib/module-loader/load.ts @@ -0,0 +1,22 @@ +import type { LoadedModule } from './interfaces.js' + +import { pathToFileURL } from 'node:url' + +import { toAbsoluteFilePath } from './absolute-file-path.js' +import { missingDefaultExportError } from './errors.js' + +export const loadModule = async (path: string): Promise => { + const absoluteFilePath = toAbsoluteFilePath(path) + const importedConfig = (await import(pathToFileURL(absoluteFilePath).href)) as { + default?: unknown + } + + if (!('default' in importedConfig)) { + throw new Error(missingDefaultExportError) + } + + return { + path: absoluteFilePath, + value: importedConfig.default, + } +} diff --git a/ui/design-storybook/src/lib/package-dir/constants.ts b/ui/design-storybook/src/lib/package-dir/constants.ts new file mode 100644 index 000000000..e55baeaf5 --- /dev/null +++ b/ui/design-storybook/src/lib/package-dir/constants.ts @@ -0,0 +1 @@ +export const packageManifestFileName = 'package.json' diff --git a/ui/design-storybook/src/lib/package-dir/package-dir.ts b/ui/design-storybook/src/lib/package-dir/package-dir.ts new file mode 100644 index 000000000..283d99da8 --- /dev/null +++ b/ui/design-storybook/src/lib/package-dir/package-dir.ts @@ -0,0 +1,10 @@ +import { createRequire } from 'node:module' +import { dirname } from 'node:path' +import { join } from 'node:path' + +import { packageManifestFileName } from './constants.js' + +const require = createRequire(import.meta.url) + +export const toPackageDir = (packageName: string): string => + dirname(require.resolve(join(packageName, packageManifestFileName))) diff --git a/ui/design-storybook/src/lib/typescript-source/constants.ts b/ui/design-storybook/src/lib/typescript-source/constants.ts new file mode 100644 index 000000000..1600d4d76 --- /dev/null +++ b/ui/design-storybook/src/lib/typescript-source/constants.ts @@ -0,0 +1,5 @@ +export const currentDirectorySpecifier = '.' +export const parentDirectorySpecifier = '..' +export const relativeDirectorySpecifierPrefix = './' +export const posixPathSeparator = '/' +export const windowsPathSeparator = '\\' diff --git a/ui/design-storybook/src/lib/typescript-source/interfaces.ts b/ui/design-storybook/src/lib/typescript-source/interfaces.ts new file mode 100644 index 000000000..d58f3a5d1 --- /dev/null +++ b/ui/design-storybook/src/lib/typescript-source/interfaces.ts @@ -0,0 +1,4 @@ +export interface ImportSpecifier { + exportName: string + module: string +} diff --git a/ui/design-storybook/src/lib/typescript-source/mappers/import-statement.ts b/ui/design-storybook/src/lib/typescript-source/mappers/import-statement.ts new file mode 100644 index 000000000..d8b3a85d9 --- /dev/null +++ b/ui/design-storybook/src/lib/typescript-source/mappers/import-statement.ts @@ -0,0 +1,27 @@ +import type { ImportSpecifier } from '../interfaces.js' + +import { toModuleSpecifier } from './module-specifier.js' +import { toSourceLiteral } from './source-literal.js' + +const toImportBinding = (specifier: ImportSpecifier, localName: string): string => { + if (specifier.exportName === 'default') { + return localName + } + + if (specifier.exportName === localName) { + return `{ ${localName} }` + } + + return `{ ${specifier.exportName} as ${localName} }` +} + +export const toImportStatement = ( + specifier: ImportSpecifier, + localName: string, + fromFile: string +): string => { + const moduleSpecifier = toModuleSpecifier(specifier.module, fromFile) + const importBinding = toImportBinding(specifier, localName) + + return `import ${importBinding} from ${toSourceLiteral(moduleSpecifier)}` +} diff --git a/ui/design-storybook/src/lib/typescript-source/mappers/module-specifier.ts b/ui/design-storybook/src/lib/typescript-source/mappers/module-specifier.ts new file mode 100644 index 000000000..8ae0c102e --- /dev/null +++ b/ui/design-storybook/src/lib/typescript-source/mappers/module-specifier.ts @@ -0,0 +1,34 @@ +import { dirname } from 'node:path' +import { isAbsolute } from 'node:path' +import { relative } from 'node:path' +import { resolve } from 'node:path' + +import { currentDirectorySpecifier } from '../constants.js' +import { parentDirectorySpecifier } from '../constants.js' +import { posixPathSeparator } from '../constants.js' +import { relativeDirectorySpecifierPrefix } from '../constants.js' +import { windowsPathSeparator } from '../constants.js' + +const isPathSpecifier = (value: string): boolean => + value.startsWith(currentDirectorySpecifier) || + value.startsWith(posixPathSeparator) || + value.startsWith(parentDirectorySpecifier) + +const normalizeRelativeSpecifier = (value: string): string => { + const normalized = value.replaceAll(windowsPathSeparator, posixPathSeparator) + + return normalized.startsWith(currentDirectorySpecifier) + ? normalized + : `${relativeDirectorySpecifierPrefix}${normalized}` +} + +export const toModuleSpecifier = (value: string, fromFile: string): string => { + if (!isPathSpecifier(value)) { + return value + } + + const absolutePath = isAbsolute(value) ? value : resolve(process.cwd(), value) + const relativePath = relative(dirname(fromFile), absolutePath) + + return normalizeRelativeSpecifier(relativePath) +} diff --git a/ui/design-storybook/src/lib/typescript-source/mappers/source-literal.ts b/ui/design-storybook/src/lib/typescript-source/mappers/source-literal.ts new file mode 100644 index 000000000..57e1ba806 --- /dev/null +++ b/ui/design-storybook/src/lib/typescript-source/mappers/source-literal.ts @@ -0,0 +1 @@ +export const toSourceLiteral = (value: unknown): string => JSON.stringify(value) diff --git a/ui/design-storybook/src/storybook/addons/config.ts b/ui/design-storybook/src/storybook/addons/config.ts new file mode 100644 index 000000000..9757ca207 --- /dev/null +++ b/ui/design-storybook/src/storybook/addons/config.ts @@ -0,0 +1,56 @@ +import type { StorybookConfig } from '@storybook/react-webpack5' + +import { createRequire } from 'node:module' + +import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' + +import { toPackageDir } from '../../lib/package-dir/package-dir.js' +import { chromaticStorybookAddon } from './constants.js' +import { storybookDocsAddon } from './constants.js' +import { storybookLinksAddon } from './constants.js' +import { storybookStylingWebpackAddon } from './constants.js' +import { storybookSwcCompilerAddon } from './constants.js' +import { cssLoaderTest } from './regex.js' +import { vanillaCssExclude } from './regex.js' +import { vanillaCssLoaderTest } from './regex.js' + +const require = createRequire(import.meta.url) + +export const createDesignStorybookAddons = (): NonNullable => [ + toPackageDir(storybookSwcCompilerAddon), + toPackageDir(storybookLinksAddon), + toPackageDir(storybookDocsAddon), + toPackageDir(chromaticStorybookAddon), + { + name: toPackageDir(storybookStylingWebpackAddon), + options: { + plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()], + rules: [ + { + test: cssLoaderTest, + sideEffects: true, + use: [ + require.resolve('style-loader'), + { + loader: require.resolve('css-loader'), + options: {}, + }, + ], + exclude: vanillaCssExclude, + }, + { + test: vanillaCssLoaderTest, + sideEffects: true, + use: [ + MiniCssExtractPlugin.loader, + { + loader: require.resolve('css-loader'), + options: { url: false }, + }, + ], + }, + ], + }, + }, +] diff --git a/ui/design-storybook/src/storybook/addons/constants.ts b/ui/design-storybook/src/storybook/addons/constants.ts new file mode 100644 index 000000000..df0b205c5 --- /dev/null +++ b/ui/design-storybook/src/storybook/addons/constants.ts @@ -0,0 +1,5 @@ +export const storybookSwcCompilerAddon = '@storybook/addon-webpack5-compiler-swc' +export const storybookLinksAddon = '@storybook/addon-links' +export const storybookDocsAddon = '@storybook/addon-docs' +export const chromaticStorybookAddon = '@chromatic-com/storybook' +export const storybookStylingWebpackAddon = '@storybook/addon-styling-webpack' diff --git a/ui/design-storybook/src/storybook/addons/index.ts b/ui/design-storybook/src/storybook/addons/index.ts new file mode 100644 index 000000000..85c130321 --- /dev/null +++ b/ui/design-storybook/src/storybook/addons/index.ts @@ -0,0 +1 @@ +export { createDesignStorybookAddons } from './config.js' diff --git a/ui/design-storybook/src/storybook/addons/regex.ts b/ui/design-storybook/src/storybook/addons/regex.ts new file mode 100644 index 000000000..f4e8d8e57 --- /dev/null +++ b/ui/design-storybook/src/storybook/addons/regex.ts @@ -0,0 +1,3 @@ +export const cssLoaderTest = /\.css$/ +export const vanillaCssLoaderTest = /\.vanilla\.css$/i +export const vanillaCssExclude = /\.vanilla\.css$/ diff --git a/ui/design-storybook/src/storybook/main/config.ts b/ui/design-storybook/src/storybook/main/config.ts new file mode 100644 index 000000000..12e24a482 --- /dev/null +++ b/ui/design-storybook/src/storybook/main/config.ts @@ -0,0 +1,48 @@ +import type { StorybookConfig } from '@storybook/react-webpack5' + +import type { DesignStorybookConfig } from '../../config/index.js' +import type { WebpackConfig } from './interfaces.js' + +import { toPackageDir } from '../../lib/package-dir/package-dir.js' +import { createDesignStorybookAddons } from '../addons/index.js' +import { javascriptExtension } from './constants.js' +import { javascriptExtensionAlias } from './constants.js' +import { storybookReactWebpackPackage } from './constants.js' +import { webpackFallbacks } from './constants.js' + +const applyBaseWebpackConfig = (webpackConfig: WebpackConfig): WebpackConfig => { + webpackConfig.resolve = { + ...webpackConfig.resolve, + extensionAlias: { + ...webpackConfig.resolve?.extensionAlias, + [javascriptExtension]: javascriptExtensionAlias, + }, + } + + webpackConfig.resolve.fallback = { + ...webpackConfig.resolve.fallback, + ...webpackFallbacks, + } + + return webpackConfig +} + +export const createDesignStorybookMain = ({ + addons = [], + refs, + stories, + webpackFinal, +}: DesignStorybookConfig): StorybookConfig => ({ + stories, + addons: [...createDesignStorybookAddons(), ...addons], + framework: { + name: toPackageDir(storybookReactWebpackPackage), + options: {}, + }, + refs, + webpackFinal: async (webpackConfig, options) => { + const baseWebpackConfig = applyBaseWebpackConfig(webpackConfig) + + return webpackFinal ? webpackFinal(baseWebpackConfig, options) : baseWebpackConfig + }, +}) diff --git a/ui/design-storybook/src/storybook/main/constants.ts b/ui/design-storybook/src/storybook/main/constants.ts new file mode 100644 index 000000000..4d6d9b4c3 --- /dev/null +++ b/ui/design-storybook/src/storybook/main/constants.ts @@ -0,0 +1,11 @@ +export const storybookReactWebpackPackage = '@storybook/react-webpack5' +export const designStorybookMainModule = '@atls-ui/design-storybook/main' + +export const javascriptExtension = '.js' +export const javascriptExtensionAlias = ['.ts', '.tsx', '.js'] + +export const webpackFallbacks = { + assert: false, + url: false, + zlib: false, +} as const diff --git a/ui/design-storybook/src/storybook/main/index.ts b/ui/design-storybook/src/storybook/main/index.ts new file mode 100644 index 000000000..c68d46846 --- /dev/null +++ b/ui/design-storybook/src/storybook/main/index.ts @@ -0,0 +1 @@ +export { createDesignStorybookMain } from './config.js' diff --git a/ui/design-storybook/src/storybook/main/interfaces.ts b/ui/design-storybook/src/storybook/main/interfaces.ts new file mode 100644 index 000000000..5d8437682 --- /dev/null +++ b/ui/design-storybook/src/storybook/main/interfaces.ts @@ -0,0 +1,4 @@ +import type { StorybookConfig } from '@storybook/react-webpack5' + +export type WebpackFinal = NonNullable +export type WebpackConfig = Parameters[0] diff --git a/ui/design-storybook/src/storybook/main/source.ts b/ui/design-storybook/src/storybook/main/source.ts new file mode 100644 index 000000000..e838d0ec2 --- /dev/null +++ b/ui/design-storybook/src/storybook/main/source.ts @@ -0,0 +1,28 @@ +import type { LoadedDesignConfig } from '../project/interfaces.js' + +import { join } from 'node:path' + +import { toModuleSpecifier } from '../../lib/typescript-source/mappers/module-specifier.js' +import { toSourceLiteral } from '../../lib/typescript-source/mappers/source-literal.js' +import { generatedMatrixStoryName } from '../matrix-story/constants.js' +import { generatedConfigImportName } from '../project/constants.js' +import { mainFileName } from '../project/constants.js' +import { designStorybookMainModule } from './constants.js' + +export const mainSource = ({ config, path }: LoadedDesignConfig, configDir: string): string => { + const mainPath = join(configDir, mainFileName) + const configImportPath = toModuleSpecifier(path, mainPath) + const baseStories = Array.isArray(config.stories) ? config.stories : [config.stories] + const stories = config.matrix?.components.length + ? [...baseStories, `./${generatedMatrixStoryName}`] + : baseStories + + return `import ${generatedConfigImportName} from ${toSourceLiteral(configImportPath)} +import { createDesignStorybookMain } from '${designStorybookMainModule}' + +export default createDesignStorybookMain({ + ...${generatedConfigImportName}, + stories: ${JSON.stringify(stories, null, 2)}, +}) +` +} diff --git a/ui/design-storybook/src/storybook/matrix-story/constants.ts b/ui/design-storybook/src/storybook/matrix-story/constants.ts new file mode 100644 index 000000000..3e66f1f49 --- /dev/null +++ b/ui/design-storybook/src/storybook/matrix-story/constants.ts @@ -0,0 +1,11 @@ +export const generatedMatrixStoryName = 'matrix.stories.tsx' +export const storybookReactModule = '@storybook/react' +export const metaTypeName = 'Meta' +export const storyObjTypeName = 'StoryObj' +export const componentsMatrixModule = '@atls-ui/design-storybook/ui/matrix' +export const componentsMatrixName = 'ComponentsMatrix' +export const matrixStoryName = 'Matrix' +export const storyTypeName = 'Story' +export const componentsCollectionName = 'components' +export const componentLocalNamePrefix = 'Component' +export const variantsLocalNamePrefix = 'componentVariants' diff --git a/ui/design-storybook/src/storybook/matrix-story/interfaces.ts b/ui/design-storybook/src/storybook/matrix-story/interfaces.ts new file mode 100644 index 000000000..4b0a60fe1 --- /dev/null +++ b/ui/design-storybook/src/storybook/matrix-story/interfaces.ts @@ -0,0 +1,4 @@ +export interface MatrixStoryImportNames { + component: string + variants: string +} diff --git a/ui/design-storybook/src/storybook/matrix-story/source.ts b/ui/design-storybook/src/storybook/matrix-story/source.ts new file mode 100644 index 000000000..ddc9a8b39 --- /dev/null +++ b/ui/design-storybook/src/storybook/matrix-story/source.ts @@ -0,0 +1,92 @@ +import type { MatrixItemConfig } from '../../config/index.js' +import type { LoadedDesignConfig } from '../project/interfaces.js' +import type { MatrixStoryImportNames } from './interfaces.js' + +import { join } from 'node:path' + +import { toImportStatement } from '../../lib/typescript-source/mappers/import-statement.js' +import { toSourceLiteral } from '../../lib/typescript-source/mappers/source-literal.js' +import { defaultMatrixTitle } from '../../ui/matrix/constants.js' +import { componentLocalNamePrefix } from './constants.js' +import { componentsCollectionName } from './constants.js' +import { componentsMatrixModule } from './constants.js' +import { componentsMatrixName } from './constants.js' +import { generatedMatrixStoryName } from './constants.js' +import { matrixStoryName } from './constants.js' +import { metaTypeName } from './constants.js' +import { storybookReactModule } from './constants.js' +import { storyObjTypeName } from './constants.js' +import { storyTypeName } from './constants.js' +import { variantsLocalNamePrefix } from './constants.js' + +const toImportNames = (index: number): MatrixStoryImportNames => ({ + component: `${componentLocalNamePrefix}${index}`, + variants: `${variantsLocalNamePrefix}${index}`, +}) + +const createComponentImports = ( + { component, variants }: MatrixItemConfig, + index: number, + storyPath: string +): string => { + const importNames = toImportNames(index) + + return [ + toImportStatement(component, importNames.component, storyPath), + toImportStatement(variants, importNames.variants, storyPath), + ].join('\n') +} + +const createComponentItem = ({ name }: MatrixItemConfig, index: number): string => { + const importNames = toImportNames(index) + + return ` { + name: ${toSourceLiteral(name)}, + component: ${importNames.component}, + variants: ${importNames.variants}, + }` +} + +export const matrixStorySource = ( + { config }: LoadedDesignConfig, + configDir: string +): string | undefined => { + if (!config.matrix?.components.length) { + return undefined + } + + const storyPath = join(configDir, generatedMatrixStoryName) + const { components, title = defaultMatrixTitle } = config.matrix + const imports = components + .map((componentConfig, index) => createComponentImports(componentConfig, index, storyPath)) + .join('\n') + const componentItems = components + .map((componentConfig, index) => createComponentItem(componentConfig, index)) + .join(',\n') + + return `import type { ${metaTypeName} } from '${storybookReactModule}' +import type { ${storyObjTypeName} } from '${storybookReactModule}' + +import { ${componentsMatrixName} } from '${componentsMatrixModule}' + +${imports} + +const meta: ${metaTypeName} = { + title: ${toSourceLiteral(title)}, +} + +export default meta + +type ${storyTypeName} = ${storyObjTypeName} + +const ${componentsCollectionName} = [ +${componentItems}, +] + +export const ${matrixStoryName}: ${storyTypeName} = { + render: () => ( + <${componentsMatrixName} components={${componentsCollectionName}} /> + ), +} +` +} diff --git a/ui/design-storybook/src/storybook/preview/config.ts b/ui/design-storybook/src/storybook/preview/config.ts new file mode 100644 index 000000000..650728a19 --- /dev/null +++ b/ui/design-storybook/src/storybook/preview/config.ts @@ -0,0 +1,16 @@ +import type { Preview } from '@storybook/react' + +import type { DesignStorybookConfig } from '../../config/index.js' + +import { basePreviewParameters } from './constants.js' + +export const createDesignStorybookPreview = ({ + decorators, + parameters, +}: Pick = {}): Preview => ({ + decorators, + parameters: { + ...basePreviewParameters, + ...parameters, + }, +}) diff --git a/ui/design-storybook/src/storybook/preview/constants.ts b/ui/design-storybook/src/storybook/preview/constants.ts new file mode 100644 index 000000000..cf0cf0935 --- /dev/null +++ b/ui/design-storybook/src/storybook/preview/constants.ts @@ -0,0 +1,15 @@ +import type { Preview } from '@storybook/react' + +import { colorControlMatcher } from './regex.js' +import { dateControlMatcher } from './regex.js' + +export const designStorybookPreviewModule = '@atls-ui/design-storybook/preview' + +export const basePreviewParameters: Preview['parameters'] = { + controls: { + matchers: { + color: colorControlMatcher, + date: dateControlMatcher, + }, + }, +} diff --git a/ui/design-storybook/src/storybook/preview/index.ts b/ui/design-storybook/src/storybook/preview/index.ts new file mode 100644 index 000000000..0176c9180 --- /dev/null +++ b/ui/design-storybook/src/storybook/preview/index.ts @@ -0,0 +1 @@ +export { createDesignStorybookPreview } from './config.js' diff --git a/ui/design-storybook/src/storybook/preview/regex.ts b/ui/design-storybook/src/storybook/preview/regex.ts new file mode 100644 index 000000000..c1a371c57 --- /dev/null +++ b/ui/design-storybook/src/storybook/preview/regex.ts @@ -0,0 +1,2 @@ +export const colorControlMatcher = /(background|color)$/i +export const dateControlMatcher = /Date$/i diff --git a/ui/design-storybook/src/storybook/preview/source.ts b/ui/design-storybook/src/storybook/preview/source.ts new file mode 100644 index 000000000..a8be92d20 --- /dev/null +++ b/ui/design-storybook/src/storybook/preview/source.ts @@ -0,0 +1,27 @@ +import type { LoadedDesignConfig } from '../project/interfaces.js' + +import { join } from 'node:path' + +import { toModuleSpecifier } from '../../lib/typescript-source/mappers/module-specifier.js' +import { toSourceLiteral } from '../../lib/typescript-source/mappers/source-literal.js' +import { generatedConfigImportName } from '../project/constants.js' +import { previewFileName } from '../project/constants.js' +import { designStorybookPreviewModule } from './constants.js' + +export const previewSource = ({ config, path }: LoadedDesignConfig, configDir: string): string => { + const previewPath = join(configDir, previewFileName) + const configImportPath = toModuleSpecifier(path, previewPath) + const styleImports = (config.styleEntrypoints ?? []) + .map((entrypoint) => { + const importPath = toModuleSpecifier(entrypoint, previewPath) + + return `import ${toSourceLiteral(importPath)}` + }) + .join('\n') + + return `${styleImports ? `${styleImports}\n` : ''}import ${generatedConfigImportName} from ${toSourceLiteral(configImportPath)} +import { createDesignStorybookPreview } from '${designStorybookPreviewModule}' + +export default createDesignStorybookPreview(${generatedConfigImportName}) +` +} diff --git a/ui/design-storybook/src/storybook/project/constants.ts b/ui/design-storybook/src/storybook/project/constants.ts new file mode 100644 index 000000000..c40470f5c --- /dev/null +++ b/ui/design-storybook/src/storybook/project/constants.ts @@ -0,0 +1,3 @@ +export const mainFileName = 'main.ts' +export const previewFileName = 'preview.tsx' +export const generatedConfigImportName = 'designConfig' diff --git a/ui/design-storybook/src/storybook/project/files.ts b/ui/design-storybook/src/storybook/project/files.ts new file mode 100644 index 000000000..5942f2a5b --- /dev/null +++ b/ui/design-storybook/src/storybook/project/files.ts @@ -0,0 +1,40 @@ +import type { StorybookProjectSources } from './interfaces.js' +import type { LoadedDesignConfig } from './interfaces.js' +import type { StorybookProjectOptions } from './interfaces.js' + +import { mkdir } from 'node:fs/promises' +import { rm } from 'node:fs/promises' +import { writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { resolve } from 'node:path' + +import { mainSource } from '../main/source.js' +import { generatedMatrixStoryName } from '../matrix-story/constants.js' +import { matrixStorySource } from '../matrix-story/source.js' +import { previewSource } from '../preview/source.js' +import { mainFileName } from './constants.js' +import { previewFileName } from './constants.js' + +export const writeStorybookFiles = async ( + loadedConfig: LoadedDesignConfig, + options: StorybookProjectOptions +): Promise => { + const configDir = resolve(process.cwd(), options.configDir) + const sources: StorybookProjectSources = { + main: mainSource(loadedConfig, configDir), + matrixStory: matrixStorySource(loadedConfig, configDir), + preview: previewSource(loadedConfig, configDir), + } + + await mkdir(configDir, { recursive: true }) + await writeFile(join(configDir, mainFileName), sources.main) + await writeFile(join(configDir, previewFileName), sources.preview) + + if (sources.matrixStory) { + await writeFile(join(configDir, generatedMatrixStoryName), sources.matrixStory) + } else { + await rm(join(configDir, generatedMatrixStoryName), { force: true }) + } + + return configDir +} diff --git a/ui/design-storybook/src/storybook/project/interfaces.ts b/ui/design-storybook/src/storybook/project/interfaces.ts new file mode 100644 index 000000000..e93330b40 --- /dev/null +++ b/ui/design-storybook/src/storybook/project/interfaces.ts @@ -0,0 +1,16 @@ +import type { DesignStorybookConfig } from '../../config/index.js' + +export interface LoadedDesignConfig { + config: DesignStorybookConfig + path: string +} + +export interface StorybookProjectOptions { + configDir: string +} + +export interface StorybookProjectSources { + main: string + matrixStory?: string + preview: string +} diff --git a/ui/design-storybook/src/storybook/runtime/constants.ts b/ui/design-storybook/src/storybook/runtime/constants.ts new file mode 100644 index 000000000..aad17c890 --- /dev/null +++ b/ui/design-storybook/src/storybook/runtime/constants.ts @@ -0,0 +1,8 @@ +export const storybookBuildCommand = 'build' +export const storybookDevCommand = 'dev' +export const storybookPackageName = 'storybook' +export const storybookPackageManifest = 'package.json' +export const packageJsonEncoding = 'utf8' +export const configDirOption = '--config-dir' +export const outputDirOption = '-o' +export const portOption = '-p' diff --git a/ui/design-storybook/src/storybook/runtime/errors.ts b/ui/design-storybook/src/storybook/runtime/errors.ts new file mode 100644 index 000000000..95237ad47 --- /dev/null +++ b/ui/design-storybook/src/storybook/runtime/errors.ts @@ -0,0 +1,4 @@ +const unknownExitCode = 'unknown' + +export const createStorybookFailedError = (code: number | null): Error => + new Error(`storybook_failed:${code ?? unknownExitCode}`) diff --git a/ui/design-storybook/src/storybook/runtime/interfaces.ts b/ui/design-storybook/src/storybook/runtime/interfaces.ts new file mode 100644 index 000000000..2dc8a2744 --- /dev/null +++ b/ui/design-storybook/src/storybook/runtime/interfaces.ts @@ -0,0 +1,20 @@ +export type StorybookArgs = Array +export type StorybookCommand = 'build' | 'dev' + +export interface StorybookPackageJson { + bin: string +} + +export interface StorybookCommandArgsParams { + command: StorybookCommand + configDir: string + outputDir: string + port: string +} + +export interface StorybookRuntimeOptions { + command: StorybookCommand + configDir: string + outputDir: string + port: string +} diff --git a/ui/design-storybook/src/storybook/runtime/run.ts b/ui/design-storybook/src/storybook/runtime/run.ts new file mode 100644 index 000000000..c3b0e0513 --- /dev/null +++ b/ui/design-storybook/src/storybook/runtime/run.ts @@ -0,0 +1,77 @@ +import type { StorybookArgs } from './interfaces.js' +import type { StorybookCommandArgsParams } from './interfaces.js' +import type { StorybookPackageJson } from './interfaces.js' +import type { StorybookRuntimeOptions } from './interfaces.js' + +import { spawn } from 'node:child_process' +import { readFile } from 'node:fs/promises' +import { createRequire } from 'node:module' +import { dirname } from 'node:path' +import { join } from 'node:path' +import { resolve } from 'node:path' + +import { configDirOption } from './constants.js' +import { outputDirOption } from './constants.js' +import { packageJsonEncoding } from './constants.js' +import { portOption } from './constants.js' +import { storybookBuildCommand } from './constants.js' +import { storybookDevCommand } from './constants.js' +import { storybookPackageManifest } from './constants.js' +import { storybookPackageName } from './constants.js' +import { createStorybookFailedError } from './errors.js' + +const requireFromRuntime = createRequire(import.meta.url) + +const storybookBinaryPath = async (): Promise => { + const packageJsonPath = requireFromRuntime.resolve( + join(storybookPackageName, storybookPackageManifest) + ) + const packageJson = JSON.parse( + await readFile(packageJsonPath, packageJsonEncoding) + ) as StorybookPackageJson + + return join(dirname(packageJsonPath), packageJson.bin) +} + +const storybookCommandArgs = ({ + command, + configDir, + outputDir, + port, +}: StorybookCommandArgsParams): StorybookArgs => + command === storybookBuildCommand + ? [ + storybookBuildCommand, + configDirOption, + configDir, + outputDirOption, + resolve(process.cwd(), outputDir), + ] + : [storybookDevCommand, configDirOption, configDir, portOption, port] + +export const runStorybook = async (options: StorybookRuntimeOptions): Promise => { + const args = storybookCommandArgs({ + command: options.command, + configDir: options.configDir, + outputDir: options.outputDir, + port: options.port, + }) + const binaryPath = await storybookBinaryPath() + + await new Promise((resolvePromise, rejectPromise) => { + const child = spawn(process.execPath, [binaryPath, ...args], { + stdio: 'inherit', + }) + + child.on('error', rejectPromise) + child.on('exit', (code) => { + if (code === 0) { + resolvePromise() + + return + } + + rejectPromise(createStorybookFailedError(code)) + }) + }) +} diff --git a/ui/design-storybook/src/ui/matrix/component.tsx b/ui/design-storybook/src/ui/matrix/component.tsx new file mode 100644 index 000000000..96880a374 --- /dev/null +++ b/ui/design-storybook/src/ui/matrix/component.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react' + +import type { ComponentsMatrixProps } from './interfaces.js' + +import { defaultMatrixTitle } from './constants.js' +import { matrixStyles } from './styles.css.js' + +export const ComponentsMatrix = ({ + components, + title = defaultMatrixTitle, +}: ComponentsMatrixProps): ReactNode => ( +
+

{title}

+ {components.map(({ component: MatrixComponent, name, variants }) => ( +
+

{name}

+
+ {variants.map(({ children, label, props = {} }) => ( +
+ {label} + {children} +
+ ))} +
+
+ ))} +
+) diff --git a/ui/design-storybook/src/ui/matrix/constants.ts b/ui/design-storybook/src/ui/matrix/constants.ts new file mode 100644 index 000000000..5355517f9 --- /dev/null +++ b/ui/design-storybook/src/ui/matrix/constants.ts @@ -0,0 +1,2 @@ +export const defaultMatrixTitle = 'Hyperion/Components Matrix' +export const matrixCellMinWidth = '180px' diff --git a/ui/design-storybook/src/ui/matrix/index.ts b/ui/design-storybook/src/ui/matrix/index.ts new file mode 100644 index 000000000..0f4a47b17 --- /dev/null +++ b/ui/design-storybook/src/ui/matrix/index.ts @@ -0,0 +1,3 @@ +export * from './constants.js' +export * from './component.js' +export type * from './interfaces.js' diff --git a/ui/design-storybook/src/ui/matrix/interfaces.ts b/ui/design-storybook/src/ui/matrix/interfaces.ts new file mode 100644 index 000000000..85cb88739 --- /dev/null +++ b/ui/design-storybook/src/ui/matrix/interfaces.ts @@ -0,0 +1,19 @@ +import type { ElementType } from 'react' +import type { ReactNode } from 'react' + +export interface ComponentsMatrixVariant { + children?: ReactNode + label: string + props?: Record +} + +export interface ComponentsMatrixItem { + component: ElementType + name: string + variants: Array +} + +export interface ComponentsMatrixProps { + components: Array + title?: string +} diff --git a/ui/design-storybook/src/ui/matrix/styles.css.ts b/ui/design-storybook/src/ui/matrix/styles.css.ts new file mode 100644 index 000000000..1dbc67451 --- /dev/null +++ b/ui/design-storybook/src/ui/matrix/styles.css.ts @@ -0,0 +1,44 @@ +import { style } from '@vanilla-extract/css' + +import { vars } from '@atls-ui-parts/theme' + +import { matrixCellMinWidth } from './constants.js' + +export const matrixStyles = { + root: style({ + display: 'grid', + gap: vars.space.g24, + padding: vars.space.g24, + }), + title: style({ + fontFamily: vars.fonts.primary, + fontSize: vars.fontSizes.regular, + fontWeight: vars.fontWeights.bold, + margin: vars.space.zero, + }), + section: style({ + display: 'grid', + gap: vars.space.g12, + }), + sectionTitle: style({ + fontFamily: vars.fonts.primary, + fontSize: vars.fontSizes.semiBold, + fontWeight: vars.fontWeights.bold, + margin: vars.space.zero, + }), + grid: style({ + display: 'grid', + gap: vars.space.g12, + gridTemplateColumns: `repeat(auto-fit, minmax(${matrixCellMinWidth}, 1fr))`, + }), + cell: style({ + alignItems: 'start', + display: 'grid', + gap: vars.space.g8, + }), + label: style({ + color: vars.colors['text.almostBlack'], + fontFamily: vars.fonts.primary, + fontSize: vars.fontSizes.micro, + }), +} diff --git a/ui/design-storybook/tsconfig.json b/ui/design-storybook/tsconfig.json new file mode 100644 index 000000000..a3703ab9e --- /dev/null +++ b/ui/design-storybook/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "skipLibCheck": true + }, + "include": [ + "src/app/**/*", + "src/config/**/*", + "src/lib/**/*", + "src/storybook/**/*", + "src/ui/**/*" + ] +} diff --git a/ui/design/package.json b/ui/design/package.json index 253801146..6ce14b417 100644 --- a/ui/design/package.json +++ b/ui/design/package.json @@ -20,6 +20,7 @@ "@atls-ui-parts/link": "workspace:*", "@atls-ui-parts/text": "workspace:*", "@atls-ui-parts/theme": "workspace:*", + "@atls-ui/design-storybook": "workspace:*", "@chromatic-com/storybook": "5.2.1", "@mdx-js/react": "3.1.1", "@storybook/addon-docs": "10.4.6", diff --git a/ui/design/src/constants.ts b/ui/design/src/constants.ts index e4c01cbf4..283b36188 100644 --- a/ui/design/src/constants.ts +++ b/ui/design/src/constants.ts @@ -8,5 +8,8 @@ const UI_PARTS_URL_FALLBACK = IS_DEVELOPMENT ? 'http://localhost:3001/' : 'https://ui-parts.ui.atls.design' +export const designStorybookStories = ['**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', '**/*.mdx'] +export const uiAdminRefTitle = 'Admin' +export const uiPartsRefTitle = 'Parts' export const UI_ADMIN_URL = process.env.UI_ADMIN_URL || UI_ADMIN_URL_FALLBACK export const UI_PARTS_URL = process.env.UI_PARTS_URL || UI_PARTS_URL_FALLBACK diff --git a/ui/design/src/main.ts b/ui/design/src/main.ts index 1466ff0c7..c89ed8f3e 100644 --- a/ui/design/src/main.ts +++ b/ui/design/src/main.ts @@ -1,91 +1,20 @@ -import type { StorybookConfig } from '@storybook/react-webpack5' - -import { createRequire } from 'node:module' -import { dirname } from 'node:path' -import { join } from 'node:path' - -import { VanillaExtractPlugin } from '@vanilla-extract/webpack-plugin' -import MiniCssExtractPlugin from 'mini-css-extract-plugin' - -import { UI_ADMIN_URL } from '@atls-ui/design/constants' -import { UI_PARTS_URL } from '@atls-ui/design/constants' - -const require = createRequire(import.meta.url) - -const getAbsolutePath = (value: string): string => - dirname(require.resolve(join(value, 'package.json'))) - -const config: StorybookConfig = { - stories: ['**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)', '**/*.mdx'], - addons: [ - getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), - getAbsolutePath('@storybook/addon-links'), - getAbsolutePath('@storybook/addon-docs'), - getAbsolutePath('@chromatic-com/storybook'), - { - name: getAbsolutePath('@storybook/addon-styling-webpack'), - options: { - plugins: [new VanillaExtractPlugin(), new MiniCssExtractPlugin()], - rules: [ - { - test: /\.css$/, - sideEffects: true, - use: [ - require.resolve('style-loader'), - { - loader: require.resolve('css-loader'), - options: {}, - }, - ], - exclude: /\.vanilla\.css$/, - }, - { - test: /\.vanilla\.css$/i, - sideEffects: true, - use: [ - MiniCssExtractPlugin.loader, - { - loader: require.resolve('css-loader'), - options: { url: false }, - }, - ], - }, - ], - }, - }, - ], - framework: { - name: getAbsolutePath('@storybook/react-webpack5'), - options: {}, - }, +import { UI_ADMIN_URL } from '@atls-ui/design/constants' +import { UI_PARTS_URL } from '@atls-ui/design/constants' +import { createDesignStorybookMain } from '@atls-ui/design-storybook/main' +import { designStorybookStories } from '@atls-ui/design/constants' +import { uiAdminRefTitle } from '@atls-ui/design/constants' +import { uiPartsRefTitle } from '@atls-ui/design/constants' + +export default createDesignStorybookMain({ + stories: designStorybookStories, refs: { parts: { - title: 'Parts', + title: uiPartsRefTitle, url: UI_PARTS_URL, }, admin: { - title: 'Admin', + title: uiAdminRefTitle, url: UI_ADMIN_URL, }, }, - webpackFinal: async (webpackConfig) => { - webpackConfig.resolve = { - ...webpackConfig.resolve, - extensionAlias: { - ...webpackConfig.resolve?.extensionAlias, - '.js': ['.ts', '.tsx', '.js'], - }, - } - - if (webpackConfig.resolve.fallback) { - webpackConfig.resolve.fallback = { - ...webpackConfig.resolve.fallback, - assert: false, - url: false, - } - } - return webpackConfig - }, -} - -export default config +}) diff --git a/ui/design/src/preview.tsx b/ui/design/src/preview.tsx index a14f7f7f5..73ecdbca5 100644 --- a/ui/design/src/preview.tsx +++ b/ui/design/src/preview.tsx @@ -1,10 +1,10 @@ -import type { Preview } from '@storybook/react' -import type { ReactNode } from 'react' -import type { PropsWithChildren } from 'react' +import type { ReactNode } from 'react' +import type { PropsWithChildren } from 'react' -import { useEffect } from 'react' +import { useEffect } from 'react' -import { globalThemeId } from '@atls-ui-parts/theme' +import { globalThemeId } from '@atls-ui-parts/theme' +import { createDesignStorybookPreview } from '@atls-ui/design-storybook/preview' const ThemeContainer = ({ children }: PropsWithChildren): ReactNode => { useEffect(() => { @@ -14,20 +14,12 @@ const ThemeContainer = ({ children }: PropsWithChildren): ReactNode => { return children } -const preview: Preview = { - decorators: (Story) => ( - - - - ), - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -} - -export default preview +export default createDesignStorybookPreview({ + decorators: [ + (Story) => ( + + + + ), + ], +}) diff --git a/yarn.lock b/yarn.lock index b3efdb384..002927d53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -76,6 +76,7 @@ __metadata: version: 0.0.0-use.local resolution: "@atls-ui-admin/design@workspace:ui-admin/design" dependencies: + "@atls-ui/design-storybook": "workspace:*" "@chromatic-com/storybook": "npm:5.2.1" "@mdx-js/react": "npm:3.1.1" "@storybook/addon-docs": "npm:10.4.6" @@ -576,6 +577,7 @@ __metadata: resolution: "@atls-ui-parts/design@workspace:ui-parts/design" dependencies: "@atls-ui-parts/theme": "workspace:*" + "@atls-ui/design-storybook": "workspace:*" "@chromatic-com/storybook": "npm:5.2.1" "@mdx-js/react": "npm:3.1.1" "@storybook/addon-docs": "npm:10.4.6" @@ -1103,6 +1105,42 @@ __metadata: languageName: unknown linkType: soft +"@atls-ui/design-storybook@workspace:*, @atls-ui/design-storybook@workspace:ui/design-storybook": + version: 0.0.0-use.local + resolution: "@atls-ui/design-storybook@workspace:ui/design-storybook" + dependencies: + "@atls-ui-parts/theme": "workspace:*" + "@chromatic-com/storybook": "npm:5.2.1" + "@mdx-js/react": "npm:3.1.1" + "@storybook/addon-docs": "npm:10.4.6" + "@storybook/addon-links": "npm:10.4.6" + "@storybook/addon-styling-webpack": "npm:3.0.2" + "@storybook/addon-webpack5-compiler-swc": "npm:4.0.3" + "@storybook/react": "npm:10.4.6" + "@storybook/react-webpack5": "npm:10.4.6" + "@types/node": "npm:25.9.3" + "@types/react": "npm:19.1.2" + "@vanilla-extract/css": "npm:1.17.1" + "@vanilla-extract/dynamic": "npm:2.1.2" + "@vanilla-extract/webpack-plugin": "npm:2.3.27" + css-loader: "npm:7.1.4" + mini-css-extract-plugin: "npm:2.10.2" + react: "npm:19.1.0" + react-dom: "npm:19.1.0" + storybook: "npm:10.4.6" + style-loader: "npm:4.0.0" + typescript: "npm:5.5.4" + webpack: "npm:5.107.2" + peerDependencies: + "@vanilla-extract/css": "*" + "@vanilla-extract/dynamic": "*" + react: "*" + react-dom: "*" + bin: + hyperion-design-storybook: ./dist/app/cli.js + languageName: unknown + linkType: soft + "@atls-ui/design@workspace:ui/design": version: 0.0.0-use.local resolution: "@atls-ui/design@workspace:ui/design" @@ -1112,6 +1150,7 @@ __metadata: "@atls-ui-parts/link": "workspace:*" "@atls-ui-parts/text": "workspace:*" "@atls-ui-parts/theme": "workspace:*" + "@atls-ui/design-storybook": "workspace:*" "@chromatic-com/storybook": "npm:5.2.1" "@mdx-js/react": "npm:3.1.1" "@storybook/addon-docs": "npm:10.4.6"