diff --git a/pkgs/clan-app/README.md b/pkgs/clan-app/README.md index 8f1b81dd9..14cbcc41b 100644 --- a/pkgs/clan-app/README.md +++ b/pkgs/clan-app/README.md @@ -26,7 +26,7 @@ direnv: export +AR +AS +CC +CLAN_CORE_PATH +CONFIG_SHELL +CXX +DETERMINISTIC_BUI Once that has loaded, you can run the local dev environment by running: ``` -$ process-compose --use-uds --keep-project +$ process-compose --use-uds --keep-project -n app ``` This will start a [process-compose] instance containing two processes: @@ -50,6 +50,24 @@ From there you can start `clan-app` again with `F7`. Follow the instructions below to set up your development environment and start the application: +## Storybook + +We use [Storybook] to develop UI components. +It can be started by running the following: + +```console +$ process-compose --use-uds --keep-project -n storybook +``` + +This will start a [process-compose] instance containing two processes: + +* `storybook` which is the main [storybook] process. +* `luakit` which is a [webkit]-based browser for viewing the stories with. This is the same underlying engine used when +rendering the app. + +You can run storybook tests with `npm run test-storybook`. +If you change how a component(s) renders, +you will need to update the snapshots with `npm run test-storybook-update-snapshots`. ## Start clan-app without process-compose @@ -139,3 +157,5 @@ Here are some important documentation links related to the Clan App: [process-compose]: https://f1bonacc1.github.io/process-compose/ [vite]: https://vite.dev/ [webview]: https://github.com/webview/webview +[Storybook]: https://storybook.js.org/ +[webkit]: https://webkit.org/ \ No newline at end of file diff --git a/pkgs/clan-app/flake-module.nix b/pkgs/clan-app/flake-module.nix index a23698a87..cbcbd7f1f 100644 --- a/pkgs/clan-app/flake-module.nix +++ b/pkgs/clan-app/flake-module.nix @@ -29,6 +29,8 @@ fonts = config.packages.fonts; }; + packages.clan-app-ui-storybook = self'.packages.clan-app-ui.storybook; + checks = config.packages.clan-app.tests; }; } diff --git a/pkgs/clan-app/process-compose.yaml b/pkgs/clan-app/process-compose.yaml index 4a230f7fe..93f883022 100644 --- a/pkgs/clan-app/process-compose.yaml +++ b/pkgs/clan-app/process-compose.yaml @@ -1,7 +1,10 @@ version: "0.5" processes: + # App Dev + clan-app-ui: + namespace: "app" command: | cd $(git rev-parse --show-toplevel)/pkgs/clan-app/ui npm install @@ -9,6 +12,7 @@ processes: ready_log_line: "VITE" clan-app: + namespace: "app" command: | cd $(git rev-parse --show-toplevel)/pkgs/clan-app ./bin/clan-app --debug --content-uri http://localhost:3000 @@ -17,3 +21,19 @@ processes: condition: "process_log_ready" is_foreground: true ready_log_line: "Debug mode enabled" + + # Storybook Dev + + storybook: + namespace: "storybook" + command: | + cd $(git rev-parse --show-toplevel)/pkgs/clan-app/ui + npm run storybook-dev -- --ci + ready_log_line: "started" + + luakit: + namespace: "storybook" + command: "luakit http://localhost:6006" + depends_on: + storybook: + condition: "process_log_ready" diff --git a/pkgs/clan-app/shell.nix b/pkgs/clan-app/shell.nix index 0eaa84fdc..2825829fb 100644 --- a/pkgs/clan-app/shell.nix +++ b/pkgs/clan-app/shell.nix @@ -5,8 +5,11 @@ webview-lib, clan-app-ui, clan-ts-api, + ps, process-compose, json2ts, + playwright-driver, + luakit, self', }: let @@ -25,11 +28,14 @@ mkShell { packages = [ # required for reload-python-api.sh script json2ts + # for viewing the storybook in a webkit-based browser to match webview + luakit ]; inherit (clan-app) propagatedBuildInputs; nativeBuildInputs = clan-app.nativeBuildInputs ++ [ + ps process-compose ]; @@ -56,7 +62,6 @@ mkShell { # Add current package to PYTHONPATH export PYTHONPATH="$(pwd)''${PYTHONPATH:+:$PYTHONPATH:}" - popd # Add clan-cli to the python path so that we can import it without building it in nix first @@ -77,6 +82,18 @@ mkShell { chmod -R +w api popd + # configure playwright for storybook snapshot testing + export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + export PLAYWRIGHT_BROWSERS_PATH=${ + playwright-driver.browsers.override { + withFfmpeg = false; + withFirefox = false; + withChromium = false; + withChromiumHeadlessShell = true; + } + } + export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="ubuntu-24.04" + # configure process-compose if test -f "$GIT_ROOT/pkgs/clan-app/.local.env"; then source "$GIT_ROOT/pkgs/clan-app/.local.env" diff --git a/pkgs/clan-app/ui.nix b/pkgs/clan-app/ui.nix index 22f8bb641..b264e7492 100644 --- a/pkgs/clan-app/ui.nix +++ b/pkgs/clan-app/ui.nix @@ -2,11 +2,12 @@ buildNpmPackage, nodejs_22, importNpmLock, - clan-ts-api, + playwright-driver, + ps, fonts, }: -buildNpmPackage { +buildNpmPackage (finalAttrs: { pname = "clan-app-ui"; version = "0.0.1"; nodejs = nodejs_22; @@ -15,6 +16,7 @@ buildNpmPackage { npmDeps = importNpmLock { npmRoot = ./ui; }; + npmConfigHook = importNpmLock.npmConfigHook; preBuild = '' @@ -22,4 +24,36 @@ buildNpmPackage { cp -r ${clan-ts-api}/* api cp -r ${fonts} ".fonts" ''; -} + + passthru = rec { + storybook = buildNpmPackage { + pname = "${finalAttrs.pname}-storybook"; + inherit (finalAttrs) + version + nodejs + src + npmDeps + npmConfigHook + preBuild + ; + + nativeBuildInputs = finalAttrs.nativeBuildInputs ++ [ + ps + ]; + + npmBuildScript = "test-storybook-static"; + + env = finalAttrs.env // { + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 1; + PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers.override { + withChromiumHeadlessShell = true; + }}"; + PLAYWRIGHT_HOST_PLATFORM_OVERRIDE = "ubuntu-24.04"; + }; + + postBuild = '' + mv storybook-static $out + ''; + }; + }; +}) diff --git a/pkgs/clan-app/ui/.gitignore b/pkgs/clan-app/ui/.gitignore index 5eeb7ff46..eed390273 100644 --- a/pkgs/clan-app/ui/.gitignore +++ b/pkgs/clan-app/ui/.gitignore @@ -1,4 +1,5 @@ app/api app/.fonts -.vite \ No newline at end of file +.vite +storybook-static \ No newline at end of file diff --git a/pkgs/clan-app/ui/.storybook/main.ts b/pkgs/clan-app/ui/.storybook/main.ts index ac193c520..518caac24 100644 --- a/pkgs/clan-app/ui/.storybook/main.ts +++ b/pkgs/clan-app/ui/.storybook/main.ts @@ -12,7 +12,6 @@ const config: StorybookConfig = { addons: [ getAbsolutePath("@storybook/addon-links"), getAbsolutePath("@storybook/addon-essentials"), - getAbsolutePath("@chromatic-com/storybook"), getAbsolutePath("@storybook/addon-interactions"), ], framework: { @@ -27,6 +26,9 @@ const config: StorybookConfig = { docs: { autodocs: "tag", }, + core: { + disableTelemetry: true, + }, }; export default config; diff --git a/pkgs/clan-app/ui/.storybook/test-runner.ts b/pkgs/clan-app/ui/.storybook/test-runner.ts new file mode 100644 index 000000000..f9344480a --- /dev/null +++ b/pkgs/clan-app/ui/.storybook/test-runner.ts @@ -0,0 +1,12 @@ +import type { TestRunnerConfig } from "@storybook/test-runner"; + +const config: TestRunnerConfig = { + async postVisit(page, context) { + // the #storybook-root element wraps the story. In Storybook 6.x, the selector is #root + const elementHandler = await page.$("#storybook-root"); + const innerHTML = await elementHandler.innerHTML(); + expect(innerHTML).toMatchSnapshot(); + }, +}; + +export default config; diff --git a/pkgs/clan-app/ui/eslint.config.mjs b/pkgs/clan-app/ui/eslint.config.mjs index c837e0825..44b2bae56 100644 --- a/pkgs/clan-app/ui/eslint.config.mjs +++ b/pkgs/clan-app/ui/eslint.config.mjs @@ -2,6 +2,7 @@ import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import tailwind from "eslint-plugin-tailwindcss"; import pluginQuery from "@tanstack/eslint-plugin-query"; +import { globalIgnores } from "eslint/config"; const config = tseslint.config( eslint.configs.recommended, @@ -9,6 +10,7 @@ const config = tseslint.config( ...tseslint.configs.strict, ...tseslint.configs.stylistic, ...tailwind.configs["flat/recommended"], + globalIgnores(["src/types/index.d.ts"]), { rules: { "tailwindcss/no-contradicting-classname": [ diff --git a/pkgs/clan-app/ui/package-lock.json b/pkgs/clan-app/ui/package-lock.json index 4e8e38024..c38ba7863 100644 --- a/pkgs/clan-app/ui/package-lock.json +++ b/pkgs/clan-app/ui/package-lock.json @@ -25,7 +25,6 @@ }, "devDependencies": { "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@chromatic-com/storybook": "^3.2.6", "@eslint/js": "^9.3.0", "@kachurun/storybook-solid": "^8.6.7", "@kachurun/storybook-solid-vite": "^8.6.7", @@ -42,8 +41,10 @@ "@typescript-eslint/parser": "^8.32.1", "autoprefixer": "^10.4.19", "classnames": "^2.5.1", + "concurrently": "^9.1.2", "eslint": "^9.27.0", "eslint-plugin-tailwindcss": "^3.17.0", + "http-server": "^14.1.1", "jsdom": "^26.1.0", "postcss": "^8.4.38", "postcss-url": "^10.1.3", @@ -56,7 +57,8 @@ "vite": "^6.3.5", "vite-plugin-solid": "^2.8.2", "vite-plugin-solid-svg": "^0.8.1", - "vitest": "^3.1.4" + "vitest": "^3.1.4", + "wait-on": "^8.0.3" } }, "node_modules/@adobe/css-tools": { @@ -653,56 +655,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@chromatic-com/storybook": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-3.2.6.tgz", - "integrity": "sha512-FDmn5Ry2DzQdik+eq2sp/kJMMT36Ewe7ONXUXM2Izd97c7r6R/QyGli8eyh/F0iyqVvbLveNYFyF0dBOJNwLqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chromatic": "^11.15.0", - "filesize": "^10.0.12", - "jsonfile": "^6.1.0", - "react-confetti": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16.0.0", - "yarn": ">=1.22.18" - }, - "peerDependencies": { - "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" - } - }, - "node_modules/@chromatic-com/storybook/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@chromatic-com/storybook/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@corvu/accordion": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@corvu/accordion/-/accordion-0.2.5.tgz", @@ -4719,6 +4671,13 @@ "node": ">=4" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/async-mutex": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", @@ -4988,6 +4947,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, "node_modules/better-opn": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", @@ -5345,30 +5324,6 @@ "node": ">= 6" } }, - "node_modules/chromatic": { - "version": "11.29.0", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-11.29.0.tgz", - "integrity": "sha512-yisBlntp9hHVj19lIQdpTlcYIXuU9H/DbFuu6tyWHmj6hWT2EtukCCcxYXL78XdQt1vm2GfIrtgtKpj/Rzmo4A==", - "dev": true, - "license": "MIT", - "bin": { - "chroma": "dist/bin.js", - "chromatic": "dist/bin.js", - "chromatic-cli": "dist/bin.js" - }, - "peerDependencies": { - "@chromatic-com/cypress": "^0.*.* || ^1.0.0", - "@chromatic-com/playwright": "^0.*.* || ^1.0.0" - }, - "peerDependenciesMeta": { - "@chromatic-com/cypress": { - "optional": true - }, - "@chromatic-com/playwright": { - "optional": true - } - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -5546,6 +5501,48 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/concurrently": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz", + "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5553,6 +5550,16 @@ "dev": true, "license": "MIT" }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/corvu": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/corvu/-/corvu-0.7.2.tgz", @@ -6496,6 +6503,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6673,16 +6687,6 @@ "node": ">=16.0.0" } }, - "node_modules/filesize": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", - "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 10.4.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7260,6 +7264,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -7384,6 +7398,21 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -7398,6 +7427,73 @@ "node": ">= 14" } }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/http-server/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -8608,6 +8704,26 @@ "dev": true, "license": "ISC" }, + "node_modules/jest-process-manager/node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", @@ -9201,19 +9317,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10544,6 +10647,19 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10588,6 +10704,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -10982,6 +11108,20 @@ "node": ">=10" } }, + "node_modules/portfinder": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", + "integrity": "sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.6", + "debug": "^4.3.6" + }, + "engines": { + "node": ">= 10.12" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -11351,6 +11491,22 @@ ], "license": "MIT" }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11381,22 +11537,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-confetti": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-6.4.0.tgz", - "integrity": "sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tween-functions": "^1.2.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -11549,6 +11689,13 @@ "dev": true, "license": "ISC" }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -11799,6 +11946,13 @@ "dev": true, "license": "MIT" }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", @@ -11878,6 +12032,95 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -12896,13 +13139,6 @@ "dev": true, "license": "0BSD" }, - "node_modules/tween-functions": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz", - "integrity": "sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==", - "dev": true, - "license": "BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -13010,6 +13246,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/unist-util-is": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", @@ -13096,16 +13344,6 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unplugin": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", @@ -13160,6 +13398,13 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -13528,17 +13773,17 @@ } }, "node_modules/wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.3.tgz", + "integrity": "sha512-nQFqAFzZDeRxsu7S3C7LbuxslHhk+gnJZHyethuGKAn2IVleIbTB9I3vJSQiSR+DifUqmdzfPMoMPJfLqMF2vw==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.6.1", - "joi": "^17.11.0", + "axios": "^1.8.2", + "joi": "^17.13.3", "lodash": "^4.17.21", "minimist": "^1.2.8", - "rxjs": "^7.8.1" + "rxjs": "^7.8.2" }, "bin": { "wait-on": "bin/wait-on" diff --git a/pkgs/clan-app/ui/package.json b/pkgs/clan-app/ui/package.json index b70c61330..490d433be 100644 --- a/pkgs/clan-app/ui/package.json +++ b/pkgs/clan-app/ui/package.json @@ -11,12 +11,16 @@ "serve": "vite preview", "check": "tsc --noEmit --skipLibCheck && eslint ./src", "test": "vitest run --typecheck", - "storybook": "storybook dev -p 6006" + "storybook": "storybook", + "storybook-build": "storybook build", + "storybook-dev": "storybook dev -p 6006", + "test-storybook": "test-storybook --browsers chromium --ci", + "test-storybook-update-snapshots": "npm run test-storybook -- --updateSnapshot", + "test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'http-server storybook-static --port 6006 --silent' 'wait-on tcp:127.0.0.1:6006 && npm run test-storybook'" }, "license": "MIT", "devDependencies": { "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@chromatic-com/storybook": "^3.2.6", "@eslint/js": "^9.3.0", "@kachurun/storybook-solid": "^8.6.7", "@kachurun/storybook-solid-vite": "^8.6.7", @@ -33,8 +37,10 @@ "@typescript-eslint/parser": "^8.32.1", "autoprefixer": "^10.4.19", "classnames": "^2.5.1", + "concurrently": "^9.1.2", "eslint": "^9.27.0", "eslint-plugin-tailwindcss": "^3.17.0", + "http-server": "^14.1.1", "jsdom": "^26.1.0", "postcss": "^8.4.38", "postcss-url": "^10.1.3", @@ -47,7 +53,8 @@ "vite": "^6.3.5", "vite-plugin-solid": "^2.8.2", "vite-plugin-solid-svg": "^0.8.1", - "vitest": "^3.1.4" + "vitest": "^3.1.4", + "wait-on": "^8.0.3" }, "dependencies": { "@floating-ui/dom": "^1.6.8", diff --git a/pkgs/clan-app/ui/src/components/Button/__snapshots__/Button.stories.tsx.snap b/pkgs/clan-app/ui/src/components/Button/__snapshots__/Button.stories.tsx.snap new file mode 100644 index 000000000..38ba3e641 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/Button/__snapshots__/Button.stories.tsx.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Components/Button Default smoke-test 1`] = ` + +`; + +exports[`Components/Button Ghost smoke-test 1`] = ` + +`; + +exports[`Components/Button Light smoke-test 1`] = ` + +`; + +exports[`Components/Button Small smoke-test 1`] = ` + +`; diff --git a/pkgs/clan-app/ui/src/types/index.d.ts b/pkgs/clan-app/ui/src/types/index.d.ts new file mode 100644 index 000000000..25ed621a0 --- /dev/null +++ b/pkgs/clan-app/ui/src/types/index.d.ts @@ -0,0 +1,90 @@ +// @ts-nocheck +declare module "@kachurun/storybook-solid" { + import type { SolidRenderer } from "types"; + import type { + AnnotatedStoryFn, + Args, + ArgsFromMeta, + ArgsStoryFn, + ComponentAnnotations, + DecoratorFunction, + LoaderFunction, + ProjectAnnotations, + StoryAnnotations, + StoryContext as GenericStoryContext, + StrictArgs, + } from "@storybook/types"; + import type { Component as ComponentType, ComponentProps } from "solid-js"; + import type { SetOptional, Simplify } from "type-fest"; + export type { + ArgTypes, + Args, + Parameters, + StrictArgs, + } from "@storybook/types"; + export type { SolidRenderer }; + /** + * Metadata to configure the stories for a component. + * + * @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export) + */ + export type Meta = + TCmpOrArgs extends ComponentType + ? ComponentAnnotations> + : ComponentAnnotations; + /** + * Story function that represents a CSFv2 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ + export type StoryFn = + TCmpOrArgs extends ComponentType + ? AnnotatedStoryFn> + : AnnotatedStoryFn; + /** + * Story function that represents a CSFv3 component example. + * + * @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports) + */ + export type StoryObj = TMetaOrCmpOrArgs extends { + render?: ArgsStoryFn; + component?: infer Component; + args?: infer DefaultArgs; + } + ? Simplify< + (Component extends ComponentType + ? ComponentProps + : unknown) & + ArgsFromMeta + > extends infer TArgs + ? StoryAnnotations< + SolidRenderer, + TArgs, + SetOptional< + TArgs, + keyof TArgs & keyof (DefaultArgs & ActionArgs) + > + > + : never + : TMetaOrCmpOrArgs extends ComponentType + ? StoryAnnotations> + : StoryAnnotations; + type ActionArgs = { + [P in keyof TArgs as TArgs[P] extends (...args: any[]) => any + ? ((...args: any[]) => void) extends TArgs[P] + ? P + : never + : never]: TArgs[P]; + }; + export type Decorator = DecoratorFunction< + SolidRenderer, + TArgs + >; + export type Loader = LoaderFunction; + export type StoryContext = GenericStoryContext< + SolidRenderer, + TArgs + >; + export type Preview = ProjectAnnotations; +} +//# sourceMappingURL=index.d.ts.map