From 496f279676ba37276401cd5eecc018ea0dde59f6 Mon Sep 17 00:00:00 2001 From: examples-workflowbuilder Date: Thu, 13 Aug 2026 13:53:51 +0200 Subject: [PATCH 01/23] feat(examples): add a StackBlitz-runnable starter for the SDK (#71) * feat(examples): add a StackBlitz-runnable starter for the SDK * fix(examples): point StackBlitz file param at app.tsx * feat(docs): embed the runnable starter and fix its example nodes * fix(examples): show only the preview pane in the StackBlitz embed * fix(docs): fixed link to Stackblitz so it works after the merge --- .dockerignore | 3 + .github/workflows/pr-check.yml | 23 + README.md | 6 +- apps/docs/package.json | 2 +- apps/docs/scripts/copy-swa-config.mjs | 10 + .../src/components/stackblitz-embed.astro | 60 + apps/docs/src/config.ts | 24 + .../quick-start/wb-as-react-component.mdx | 8 + apps/docs/src/content/docs/overview/index.mdx | 4 +- apps/docs/src/middleware.ts | 35 + apps/docs/staticwebapp.config.json | 11 + examples/workflow-builder-starter/.gitignore | 3 + examples/workflow-builder-starter/README.md | 42 + examples/workflow-builder-starter/index.html | 12 + .../lint-staged.config.mjs | 7 + .../package-lock.json | 3656 +++++++++++++++++ .../workflow-builder-starter/package.json | 25 + examples/workflow-builder-starter/src/app.tsx | 22 + .../src/diagram/initial-diagram.ts | 67 + .../workflow-builder-starter/src/main.tsx | 10 + .../src/nodes/action/action.ts | 15 + .../nodes/action/default-properties-data.ts | 10 + .../src/nodes/action/schema.ts | 13 + .../src/nodes/action/uischema.ts | 31 + .../src/nodes/condition/condition.ts | 15 + .../condition/default-properties-data.ts | 10 + .../src/nodes/condition/schema.ts | 13 + .../src/nodes/condition/uischema.ts | 30 + .../src/nodes/index.ts | 9 + .../nodes/trigger/default-properties-data.ts | 9 + .../src/nodes/trigger/schema.ts | 12 + .../src/nodes/trigger/trigger.ts | 15 + .../src/nodes/trigger/uischema.ts | 21 + .../src/vite-env.d.ts | 1 + .../workflow-builder-starter/tsconfig.json | 18 + .../workflow-builder-starter/vite.config.ts | 6 + 36 files changed, 4255 insertions(+), 3 deletions(-) create mode 100644 apps/docs/scripts/copy-swa-config.mjs create mode 100644 apps/docs/src/components/stackblitz-embed.astro create mode 100644 apps/docs/src/middleware.ts create mode 100644 apps/docs/staticwebapp.config.json create mode 100644 examples/workflow-builder-starter/.gitignore create mode 100644 examples/workflow-builder-starter/README.md create mode 100644 examples/workflow-builder-starter/index.html create mode 100644 examples/workflow-builder-starter/lint-staged.config.mjs create mode 100644 examples/workflow-builder-starter/package-lock.json create mode 100644 examples/workflow-builder-starter/package.json create mode 100644 examples/workflow-builder-starter/src/app.tsx create mode 100644 examples/workflow-builder-starter/src/diagram/initial-diagram.ts create mode 100644 examples/workflow-builder-starter/src/main.tsx create mode 100644 examples/workflow-builder-starter/src/nodes/action/action.ts create mode 100644 examples/workflow-builder-starter/src/nodes/action/default-properties-data.ts create mode 100644 examples/workflow-builder-starter/src/nodes/action/schema.ts create mode 100644 examples/workflow-builder-starter/src/nodes/action/uischema.ts create mode 100644 examples/workflow-builder-starter/src/nodes/condition/condition.ts create mode 100644 examples/workflow-builder-starter/src/nodes/condition/default-properties-data.ts create mode 100644 examples/workflow-builder-starter/src/nodes/condition/schema.ts create mode 100644 examples/workflow-builder-starter/src/nodes/condition/uischema.ts create mode 100644 examples/workflow-builder-starter/src/nodes/index.ts create mode 100644 examples/workflow-builder-starter/src/nodes/trigger/default-properties-data.ts create mode 100644 examples/workflow-builder-starter/src/nodes/trigger/schema.ts create mode 100644 examples/workflow-builder-starter/src/nodes/trigger/trigger.ts create mode 100644 examples/workflow-builder-starter/src/nodes/trigger/uischema.ts create mode 100644 examples/workflow-builder-starter/src/vite-env.d.ts create mode 100644 examples/workflow-builder-starter/tsconfig.json create mode 100644 examples/workflow-builder-starter/vite.config.ts diff --git a/.dockerignore b/.dockerignore index e0fcd955c..8ec9bf851 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,6 +24,9 @@ tools/ !tools/deployment/nginx .gitignore +# examples +examples/ + # env files hold secrets — never in a build context **/.env **/.env.* diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 3c5815076..104f7e3d5 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -75,3 +75,26 @@ jobs: - name: Build run: pnpm --filter @workflowbuilder/sdk build:lib + + starter: + name: Starter build + runs-on: ubuntu-latest + defaults: + run: + working-directory: examples/workflow-builder-starter + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: examples/workflow-builder-starter/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build diff --git a/README.md b/README.md index c8aa662ff..4bec5f419 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ Drag-and-drop workflow builder UI with a reference back-end and an execution eng [Try it live ->](https://app.workflowbuilder.io/) +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/synergycodes/workflowbuilder/tree/main/examples/workflow-builder-starter?title=Workflow%20Builder%20Starter%20Example&file=src%2Fapp.tsx) + Used in production by teams including [Vercom](https://www.workflowbuilder.io/case-study/vercom), [Athena Intelligence](https://www.workflowbuilder.io/case-study/athena-intelligence), [Plura AI](https://www.workflowbuilder.io/case-study/plura-ai), and others. @@ -42,7 +44,9 @@ Three onboarding paths. Pick one based on what you want to evaluate. | See the editor running in your browser | [B. Try the demo](#path-b-try-the-demo) | ~2 min | no | | Run the full reference stack (editor + execution + AI) | [C. Run the full stack demo](#path-c-run-the-full-stack-demo) | ~10 min | yes | -Don't want to install or clone anything yet? [Try the live demo](https://app.workflowbuilder.io) in your browser first. +Don't want to install or clone anything yet? [Try the live demo](https://app.workflowbuilder.io) in your browser, or open the runnable starter below: + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/synergycodes/workflowbuilder/tree/main/examples/workflow-builder-starter?title=Workflow%20Builder%20Starter%20Example&file=src%2Fapp.tsx) ### Path A. Embed the SDK diff --git a/apps/docs/package.json b/apps/docs/package.json index 9fe55bfe4..2ace85e85 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "pnpm clean:typedoc && astro dev", - "build": "pnpm clean:typedoc && node scripts/check-sidebar-categories.mjs && astro build && node scripts/touch-distribution-index.mjs", + "build": "pnpm clean:typedoc && node scripts/check-sidebar-categories.mjs && astro build && node scripts/touch-distribution-index.mjs && node scripts/copy-swa-config.mjs", "clean:typedoc": "node -e \"import('node:fs').then(fs => fs.rmSync('src/content/docs/api', { recursive: true, force: true }))\"", "preview": "astro preview", "typecheck": "astro check", diff --git a/apps/docs/scripts/copy-swa-config.mjs b/apps/docs/scripts/copy-swa-config.mjs new file mode 100644 index 000000000..d355d25a8 --- /dev/null +++ b/apps/docs/scripts/copy-swa-config.mjs @@ -0,0 +1,10 @@ +// Azure Static Web Apps reads staticwebapp.config.json from the artifact root, +// which is `dist` — the site sits one level down in `dist/docs`. Astro's +// `public/` copies into `dist/docs`, one level too deep, so copy it here. +import { copyFileSync, mkdirSync } from 'node:fs'; + +const source = './staticwebapp.config.json'; +const target = './dist/staticwebapp.config.json'; + +mkdirSync('./dist', { recursive: true }); +copyFileSync(source, target); diff --git a/apps/docs/src/components/stackblitz-embed.astro b/apps/docs/src/components/stackblitz-embed.astro new file mode 100644 index 000000000..74241ca54 --- /dev/null +++ b/apps/docs/src/components/stackblitz-embed.astro @@ -0,0 +1,60 @@ +--- +import { STARTER_FILE, STARTER_FORK_URL, STARTER_PROJECT, STARTER_TITLE } from '../config'; + +// `corp=1` makes StackBlitz send its own isolation headers. That works only on a +// page which is itself cross-origin isolated. `staticwebapp.config.json` grants +// isolation to one route, so render this component only there. Anywhere else the +// embed shows "Unable to run Embedded Project". +const embedParams = new URLSearchParams({ + ctl: '1', + embed: '1', + corp: '1', + view: 'preview', + terminalHeight: '20', + startScript: 'dev', + title: STARTER_TITLE, + file: STARTER_FILE, +}); + +// The frame uses `/github/`, not `/fork/github/`, so it does not make a project +// per visitor. The link below the frame forks instead. +const embedUrl = `https://stackblitz.com/github/${STARTER_PROJECT}?${embedParams}`; +--- + +
+ + { + /* Full-size link, and the way out when the frame cannot start. StackBlitz + supports embeds in Chromium browsers only, and the failure is silent, so + there is nothing to detect and nothing to fall back to automatically. */ + } +

+ Open on StackBlitz +

+
+ + diff --git a/apps/docs/src/config.ts b/apps/docs/src/config.ts index e17034678..411474433 100644 --- a/apps/docs/src/config.ts +++ b/apps/docs/src/config.ts @@ -1,3 +1,27 @@ export const GITHUB_REPO_BASE = import.meta.env['GITHUB_REPO_BASE'] ?? 'https://github.com/synergycodes/workflowbuilder/blob/main/apps/demo/src/app/data/nodes'; + +/** + * The runnable starter on GitHub, as `owner/repo/tree//`. + * + * Change the ref here and both the embed and every docs link follow. The root + * README (twice) and the starter's own README repeat it, because Markdown + * cannot import a constant. + */ +export const STARTER_PROJECT = 'synergycodes/workflowbuilder/tree/main/examples/workflow-builder-starter'; + +/** File the StackBlitz editor opens. */ +export const STARTER_FILE = 'src/app.tsx'; + +/** Project name. Always send one, or StackBlitz glues owner onto repo name. */ +export const STARTER_TITLE = 'Workflow Builder Starter Example'; + +/** + * Opens an editable copy. Plain `/github/` gives no write access, so the first + * save reloads the page. + */ +export const STARTER_FORK_URL = `https://stackblitz.com/fork/github/${STARTER_PROJECT}?${new URLSearchParams({ + title: STARTER_TITLE, + file: STARTER_FILE, +})}`; diff --git a/apps/docs/src/content/docs/get-started/quick-start/wb-as-react-component.mdx b/apps/docs/src/content/docs/get-started/quick-start/wb-as-react-component.mdx index 6fe69ef3b..11f92a3b9 100644 --- a/apps/docs/src/content/docs/get-started/quick-start/wb-as-react-component.mdx +++ b/apps/docs/src/content/docs/get-started/quick-start/wb-as-react-component.mdx @@ -7,6 +7,8 @@ sidebar: import { TabItem, Tabs } from '@astrojs/starlight/components'; +import StackblitzEmbed from '@/components/stackblitz-embed.astro'; + ## Requirements - React 18 or 19 @@ -19,6 +21,12 @@ import { TabItem, Tabs } from '@astrojs/starlight/components'; Mount only one `` per page. Multi-instance is not supported. See [Side effects & limitations](/get-started/side-effects/) for the details and how to swap workflows on one page. ::: +## Try it in your browser + +A small React + Vite app that uses the SDK. Click the frame to start it, then change the code. + + + ## Installation Install the SDK along with its peer dependencies: diff --git a/apps/docs/src/content/docs/overview/index.mdx b/apps/docs/src/content/docs/overview/index.mdx index a052489cd..9929459bc 100644 --- a/apps/docs/src/content/docs/overview/index.mdx +++ b/apps/docs/src/content/docs/overview/index.mdx @@ -16,6 +16,8 @@ import { Image } from 'astro:assets'; import wbOverviewImg from '@/assets/wb-overview.webp'; +import { STARTER_FORK_URL } from '@/config'; + Workflow Builder - canvas, node palette, and properties panel Workflow Builder is a React SDK for building and embedding visual workflow editors into your application. It is available in open-source and enterprise editions. @@ -55,7 +57,7 @@ Workflow Builder is commonly used to: ## Live demo -[Open the live demo](https://app.workflowbuilder.io) to try Workflow Builder in your browser, or [contact us](https://www.workflowbuilder.io/contact) for a guided walkthrough. +[Open the live demo](https://app.workflowbuilder.io) to try Workflow Builder in your browser. To read and edit the code, open the runnable starter in StackBlitz — a minimal React app that embeds the SDK. For a guided walkthrough, [contact us](https://www.workflowbuilder.io/contact). ## See also diff --git a/apps/docs/src/middleware.ts b/apps/docs/src/middleware.ts new file mode 100644 index 000000000..80392a40c --- /dev/null +++ b/apps/docs/src/middleware.ts @@ -0,0 +1,35 @@ +import { defineMiddleware } from 'astro:middleware'; + +import swaConfig from '../staticwebapp.config.json'; + +/** + * Applies the production response headers in `astro dev`. + * + * The site builds to static files, so it cannot set its own headers. Azure + * Static Web Apps sends them in production, and this reads the same config, so + * one file owns the routes and the values. + * + * What needs them today: cross-origin isolation on the page that embeds + * StackBlitz. Without it the embed shows "Unable to run Embedded Project". + * **Do not** widen the route to the whole site - COEP blocks the YouTube embeds on + * other pages. + */ +const routes = swaConfig.routes.map(({ route, headers }) => ({ + prefix: route.replace(/\*$/, ''), + headers: Object.entries(headers), +})); + +export const onRequest = defineMiddleware(async (context, next) => { + const response = await next(); + + for (const { prefix, headers } of routes) { + if (!context.url.pathname.startsWith(prefix)) { + continue; + } + for (const [name, value] of headers) { + response.headers.set(name, value); + } + } + + return response; +}); diff --git a/apps/docs/staticwebapp.config.json b/apps/docs/staticwebapp.config.json new file mode 100644 index 000000000..33e44c3a4 --- /dev/null +++ b/apps/docs/staticwebapp.config.json @@ -0,0 +1,11 @@ +{ + "routes": [ + { + "route": "/docs/get-started/quick-start/wb-as-react-component*", + "headers": { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "credentialless" + } + } + ] +} diff --git a/examples/workflow-builder-starter/.gitignore b/examples/workflow-builder-starter/.gitignore new file mode 100644 index 000000000..804bd4a4a --- /dev/null +++ b/examples/workflow-builder-starter/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +*.local diff --git a/examples/workflow-builder-starter/README.md b/examples/workflow-builder-starter/README.md new file mode 100644 index 000000000..f2e95920a --- /dev/null +++ b/examples/workflow-builder-starter/README.md @@ -0,0 +1,42 @@ +# Workflow Builder — Starter + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/synergycodes/workflowbuilder/tree/main/examples/workflow-builder-starter?title=Workflow%20Builder%20Starter%20Example&file=src%2Fapp.tsx) + +A minimal, one-click example of [Workflow Builder](https://www.workflowbuilder.io/): a +React + TypeScript + Vite app that embeds the `@workflowbuilder/sdk` editor. + +Workflow Builder is a workflow editor **built on [React Flow](https://reactflow.dev/)**. +`` owns the React Flow canvas and adds a node palette, a +schema-driven properties panel, persistence, theming, and validation. You get a complete +editor from one component, instead of wiring React Flow by hand. + +## Run it + +```bash +npm install +npm run dev +``` + +Open the printed URL (Vite defaults to http://localhost:5173). + +## What's here + +- **`src/app.tsx`** — mounts `` with the default layout (top bar, + palette, canvas, properties panel) and `localStorage` persistence. +- **`src/nodes/`** — three example node types (`trigger`, `action`, `condition`). Each is + the canonical 4-file pattern: `schema.ts` (data), `uischema.ts` (form), + `default-properties-data.ts` (defaults), and the `PaletteItem` definition. +- **`src/diagram/initial-diagram.ts`** — a small `Trigger → Action → Condition` flow shown + on first open. + +## Try it + +- Drag a node from the palette onto the canvas. +- Click a node to edit its properties on the right. +- Connect nodes by dragging from one handle to another. + +## Next steps + +- Add your own node type by copying a folder under `src/nodes/` and registering it in + `src/nodes/index.ts`. +- Full guide and API reference: diff --git a/examples/workflow-builder-starter/index.html b/examples/workflow-builder-starter/index.html new file mode 100644 index 000000000..e9d0b79a4 --- /dev/null +++ b/examples/workflow-builder-starter/index.html @@ -0,0 +1,12 @@ + + + + + + Workflow Builder Starter + + +
+ + + diff --git a/examples/workflow-builder-starter/lint-staged.config.mjs b/examples/workflow-builder-starter/lint-staged.config.mjs new file mode 100644 index 000000000..c565d24b7 --- /dev/null +++ b/examples/workflow-builder-starter/lint-staged.config.mjs @@ -0,0 +1,7 @@ +/** + * @type {import('lint-staged').Configuration} + */ +export default { + '*.{ts,tsx,js,json,css,astro,md,mdx}': (files) => `prettier --write --log-level=silent ${files.join(' ')}`, + '*.{ts,tsx}': [(files) => `eslint --max-warnings=0 --fix ${files.join(' ')}`, () => `tsc --noEmit`], +}; diff --git a/examples/workflow-builder-starter/package-lock.json b/examples/workflow-builder-starter/package-lock.json new file mode 100644 index 000000000..4f796e635 --- /dev/null +++ b/examples/workflow-builder-starter/package-lock.json @@ -0,0 +1,3656 @@ +{ + "name": "workflow-builder-starter", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "workflow-builder-starter", + "version": "0.0.0", + "dependencies": { + "@workflowbuilder/sdk": "2.2.0", + "@xyflow/react": "12.10.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "zustand": "^5.0.1" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.3", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@fontsource/poppins": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.3.0.tgz", + "integrity": "sha512-cms1nM7U6SN8epIV1WMVOV8VsA645aSm3wHfzIQnkk188U341ThbJGSl2Sv54V29gnDbT2arMPfLzvVIIVCceQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonforms/core": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@jsonforms/core/-/core-3.8.0.tgz", + "integrity": "sha512-XSvaZuQSs/MceG5nDDcrE879onPHkGBy0xEuLeZMUkSM/M8wc1dEUrJtMOZVNSITocm9YXFY1qQ5gnsPP38zAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "ajv": "^8.18.0", + "ajv-formats": "^2.1.0", + "lodash": "^4.17.21" + } + }, + "node_modules/@jsonforms/react": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@jsonforms/react/-/react-3.8.0.tgz", + "integrity": "sha512-k81+yWLpCQl+3XizS1bLjXoBwYhW1OAkjSXFA8W5qNtfPZjSOXDgtiuMOGYDv4b60tu2e9RB8h2P2O7QhfkhiA==", + "license": "MIT", + "peer": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "peerDependencies": { + "@jsonforms/core": "3.8.0", + "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mantine/core": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.8.tgz", + "integrity": "sha512-42sfdLZSCpsCYmLCjSuntuPcDg3PLbakSmmYfz5Auea8gZYLr+8SS5k647doVu0BRAecqYOytkX2QC5/u/8VHw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.17.8", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/dates": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.8.tgz", + "integrity": "sha512-KYog/YL83PnsMef7EZagpOFq9I2gfnK0eYSzC8YvV9Mb6t/x9InqRssGWVb0GIr+TNILpEkhKoGaSKZNy10Q1g==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.17.8", + "@mantine/hooks": "7.17.8", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.8.tgz", + "integrity": "sha512-96qygbkTjRhdkzd5HDU8fMziemN/h758/EwrFu7TlWrEP10Vw076u+Ap/sG6OT4RGPZYYoHrTlT+mkCZblWHuw==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", + "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", + "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.5.0", + "@mui/system": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.9", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", + "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", + "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@phosphor-icons/react": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz", + "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@synergycodes/overflow-ui": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@synergycodes/overflow-ui/-/overflow-ui-1.0.0-beta.27.tgz", + "integrity": "sha512-Z8wDSlphLvLOA0JtzwFLrH0pO2PgmLwmG4OAmPewSh+Yet0L5dNNhWrs1AkuxkIxky74l+0Y11RVczLnuIGrAg==", + "license": "MIT", + "dependencies": { + "@emotion/styled": "^11.14.0", + "@floating-ui/react": "^0.26.28", + "@mantine/core": "^7.17.2", + "@mantine/dates": "^7.17.2", + "@mui/base": "5.0.0-beta.62", + "@mui/material": "^6.4.7", + "@phosphor-icons/react": "^2.1.7", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@synergycodes/overflow-ui/node_modules/@mui/base": { + "version": "5.0.0-beta.62", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.62.tgz", + "integrity": "sha512-TzJLCNlrMkSU4bTCdTT+TVUiGx4sjZLhH673UV6YN+rNNP8wJpkWfRSvjDB5HcbH2T0lUamnz643ZnV+8IiMjw==", + "deprecated": "This package has been replaced by @base-ui/react", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@floating-ui/react-dom": "^2.1.1", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.8", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@synergycodes/overflow-ui/node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@workflowbuilder/sdk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@workflowbuilder/sdk/-/sdk-2.2.0.tgz", + "integrity": "sha512-OyT15a/6oR8cofDMstI6Z+2owpHNgADoQUQTS8PWieysdzkhl/5+sOdoJ5va9uafSW30/ieYyGseVnwVQy1X3Q==", + "license": "Apache-2.0", + "dependencies": { + "@cfworker/json-schema": "4.1.1", + "@fontsource/poppins": "^5.2.7", + "@phosphor-icons/react": "^2.1.7", + "@synergycodes/overflow-ui": "1.0.0-beta.27", + "ace-builds": "^1.43.4", + "ajv": "^8.18.0", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "notistack": "^3.0.1", + "react-ace": "^14.0.1", + "react-mentions-ts": "^5.4.7", + "remeda": "^2.19.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@jsonforms/core": "^3.4.0", + "@jsonforms/react": "^3.4.0", + "@xyflow/react": "^12.0.0", + "i18next": "^24.0.0", + "i18next-browser-languagedetector": "^8.0.0", + "immer": "^10.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "react-i18next": "^15.0.0", + "zustand": "^5.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.0.tgz", + "integrity": "sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.74", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.74", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.74.tgz", + "integrity": "sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/ace-builds": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.44.0.tgz", + "integrity": "sha512-PFNMSYqFdEUkul2Ntud0HvA09AgY+F1ag0UYdpMH60wNI/qOA8cB8tlTgoALMEwIdUPJK2CjrIQ7OnbiSS/ugQ==", + "license": "BSD-3-Clause" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", + "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.403", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz", + "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/html-parse-stringify": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.1.0.tgz", + "integrity": "sha512-E0oAXcELOtsXe+BmpJ2EZyedbldPpriV5vICzEuo6xjC/D1lDukOI7KrpfQGF2Qc4wWEy0nk3bFORS2K5ZAhFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz", + "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", + "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/notistack": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.2.tgz", + "integrity": "sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.0", + "goober": "^2.0.33" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/notistack" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/notistack/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-ace": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-14.1.0.tgz", + "integrity": "sha512-H+Tuw1oa3uP6zRoLBOayPVaMy5Le4G33OpEmLOfSVks75ajmrmFGbyWWEHfBYLCCpu5KfyFlod9jCEUc7i5ekw==", + "license": "MIT", + "dependencies": { + "ace-builds": "^1.36.3", + "diff-match-patch": "^1.0.5", + "fast-equals": "^5.3.3", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^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", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-i18next": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.4.tgz", + "integrity": "sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.4.0", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/react-mentions-ts": { + "version": "5.4.7", + "resolved": "https://registry.npmjs.org/react-mentions-ts/-/react-mentions-ts-5.4.7.tgz", + "integrity": "sha512-bTK6joPmyvLckVf1v7vE2xSSeqvL4ZwuzFvGZpt+IrtdDOdFZjiwTUBo5920kiE6WbH/v1PP81xAo6pQ0NQ0Pg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "class-variance-authority": ">=0.6.0", + "clsx": ">=2.0.0", + "react": ">=19.0.0", + "react-dom": ">=19.0.0", + "tailwind-merge": ">=3.0.0" + } + }, + "node_modules/react-number-format": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.5.tgz", + "integrity": "sha512-y8O2yHHj3w0aE9XO8d2BCcUOOdQTRSVq+WIuMlLVucAm5XNjJAy+BoOJiuQMldVYVOKTMyvVNfnbl2Oqp+YxGw==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/remeda": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.39.0.tgz", + "integrity": "sha512-3Ki8dU1o3OVu4dwIQ2Pj+yiuP7OnEbmWAGmJ3yDRqopily5jsj8NWzPvbS89H85d6UdONKEcUnrfuHY6jN9vyw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.0.tgz", + "integrity": "sha512-x/M6q3w4Ybp91CNaS4S69UnliqR3BzRpOT6LWbksjth0S/+jhfaPJsWjt/TewpT8j9eLIojUf5jr29WextHroA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/examples/workflow-builder-starter/package.json b/examples/workflow-builder-starter/package.json new file mode 100644 index 000000000..91ee8f6cf --- /dev/null +++ b/examples/workflow-builder-starter/package.json @@ -0,0 +1,25 @@ +{ + "name": "workflow-builder-starter", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@workflowbuilder/sdk": "2.2.0", + "@xyflow/react": "12.10.0", + "react": "19.1.0", + "react-dom": "19.1.0", + "zustand": "^5.0.1" + }, + "devDependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.3", + "vite": "^6.0.7" + } +} diff --git a/examples/workflow-builder-starter/src/app.tsx b/examples/workflow-builder-starter/src/app.tsx new file mode 100644 index 000000000..2d4dcedd2 --- /dev/null +++ b/examples/workflow-builder-starter/src/app.tsx @@ -0,0 +1,22 @@ +import { WorkflowBuilder } from '@workflowbuilder/sdk'; +import type { WorkflowBuilderIsValidConnection } from '@workflowbuilder/sdk'; + +import '@workflowbuilder/sdk/style.css'; + +import { initialEdges, initialNodes } from './diagram/initial-diagram'; +import { nodeTypes } from './nodes'; + +// A trigger starts the workflow, so nothing can connect into it. +const isValidConnection: WorkflowBuilderIsValidConnection = ({ targetNode }) => targetNode.data.type !== 'trigger'; + +export function App() { + return ( + + ); +} diff --git a/examples/workflow-builder-starter/src/diagram/initial-diagram.ts b/examples/workflow-builder-starter/src/diagram/initial-diagram.ts new file mode 100644 index 000000000..545404e36 --- /dev/null +++ b/examples/workflow-builder-starter/src/diagram/initial-diagram.ts @@ -0,0 +1,67 @@ +import type { WorkflowBuilderEdge, WorkflowBuilderNode } from '@workflowbuilder/sdk'; + +export const initialNodes: WorkflowBuilderNode[] = [ + { + id: 'trigger-1', + type: 'node', + position: { x: 80, y: 176 }, + data: { + type: 'trigger', + icon: 'Lightning', + properties: { + label: 'New request', + description: 'Workflow entry point', + status: 'active', + }, + }, + }, + { + id: 'action-1', + type: 'node', + position: { x: 440, y: 176 }, + data: { + type: 'action', + icon: 'PlayCircle', + properties: { + label: 'Send email', + description: 'Notify the customer', + status: 'active', + message: 'Thanks for reaching out - we are on it!', + }, + }, + }, + { + id: 'condition-1', + type: 'node', + position: { x: 800, y: 176 }, + data: { + type: 'condition', + icon: 'Faders', + properties: { + label: 'Needs review?', + description: 'Checks the amount', + status: 'active', + condition: 'amount > 100', + }, + }, + }, +]; + +export const initialEdges: WorkflowBuilderEdge[] = [ + { + id: 'edge-trigger-action', + source: 'trigger-1', + sourceHandle: 'source', + target: 'action-1', + targetHandle: 'target', + type: 'labelEdge', + }, + { + id: 'edge-action-condition', + source: 'action-1', + sourceHandle: 'source', + target: 'condition-1', + targetHandle: 'target', + type: 'labelEdge', + }, +]; diff --git a/examples/workflow-builder-starter/src/main.tsx b/examples/workflow-builder-starter/src/main.tsx new file mode 100644 index 000000000..717c60c77 --- /dev/null +++ b/examples/workflow-builder-starter/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; + +import { App } from './app'; + +createRoot(document.querySelector('#root') as HTMLElement).render( + + + , +); diff --git a/examples/workflow-builder-starter/src/nodes/action/action.ts b/examples/workflow-builder-starter/src/nodes/action/action.ts new file mode 100644 index 000000000..5f89f77ae --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/action/action.ts @@ -0,0 +1,15 @@ +import type { PaletteItem } from '@workflowbuilder/sdk'; + +import { defaultPropertiesData } from './default-properties-data'; +import { type ActionNodeSchema, schema } from './schema'; +import { uischema } from './uischema'; + +export const action: PaletteItem = { + type: 'action', + icon: 'PlayCircle', + label: 'Action', + description: 'Performs a task', + defaultPropertiesData, + schema, + uischema, +}; diff --git a/examples/workflow-builder-starter/src/nodes/action/default-properties-data.ts b/examples/workflow-builder-starter/src/nodes/action/default-properties-data.ts new file mode 100644 index 000000000..c444f0509 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/action/default-properties-data.ts @@ -0,0 +1,10 @@ +import type { NodeDataProperties } from '@workflowbuilder/sdk'; + +import type { ActionNodeSchema } from './schema'; + +export const defaultPropertiesData: Required> = { + label: 'Action', + description: 'Performs a task', + status: 'active', + message: '', +}; diff --git a/examples/workflow-builder-starter/src/nodes/action/schema.ts b/examples/workflow-builder-starter/src/nodes/action/schema.ts new file mode 100644 index 000000000..78fa1c41d --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/action/schema.ts @@ -0,0 +1,13 @@ +import { sharedProperties, statusOptions } from '@workflowbuilder/sdk'; +import type { NodeSchema } from '@workflowbuilder/sdk'; + +export const schema = { + type: 'object', + properties: { + ...sharedProperties, + status: { type: 'string', options: Object.values(statusOptions) }, + message: { type: 'string' }, + }, +} satisfies NodeSchema; + +export type ActionNodeSchema = typeof schema; diff --git a/examples/workflow-builder-starter/src/nodes/action/uischema.ts b/examples/workflow-builder-starter/src/nodes/action/uischema.ts new file mode 100644 index 000000000..d96dd442b --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/action/uischema.ts @@ -0,0 +1,31 @@ +import type { UISchema } from '@workflowbuilder/sdk'; +import { getScope, globalControls } from '@workflowbuilder/sdk'; + +import type { ActionNodeSchema } from './schema'; + +const scope = getScope; + +const generalInformation: UISchema = { + type: 'Accordion', + label: 'General Information', + elements: [ + { type: 'Text', scope: scope('properties.label'), label: 'Title' }, + { type: 'Select', scope: scope('properties.status'), label: 'Status' }, + { type: 'Text', scope: scope('properties.description'), label: 'Description' }, + ], +}; + +export const uischema: UISchema = { + type: 'VerticalLayout', + elements: [ + ...globalControls, + generalInformation, + { + type: 'TextArea', + scope: scope('properties.message'), + label: 'Message', + placeholder: 'What should this action do?', + minRows: 4, + }, + ], +}; diff --git a/examples/workflow-builder-starter/src/nodes/condition/condition.ts b/examples/workflow-builder-starter/src/nodes/condition/condition.ts new file mode 100644 index 000000000..614dc87ce --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/condition/condition.ts @@ -0,0 +1,15 @@ +import type { PaletteItem } from '@workflowbuilder/sdk'; + +import { defaultPropertiesData } from './default-properties-data'; +import { type ConditionNodeSchema, schema } from './schema'; +import { uischema } from './uischema'; + +export const condition: PaletteItem = { + type: 'condition', + icon: 'Faders', + label: 'Condition', + description: 'Evaluates a condition', + defaultPropertiesData, + schema, + uischema, +}; diff --git a/examples/workflow-builder-starter/src/nodes/condition/default-properties-data.ts b/examples/workflow-builder-starter/src/nodes/condition/default-properties-data.ts new file mode 100644 index 000000000..1935e3922 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/condition/default-properties-data.ts @@ -0,0 +1,10 @@ +import type { NodeDataProperties } from '@workflowbuilder/sdk'; + +import type { ConditionNodeSchema } from './schema'; + +export const defaultPropertiesData: Required> = { + label: 'Condition', + description: 'Evaluates a condition', + status: 'active', + condition: '', +}; diff --git a/examples/workflow-builder-starter/src/nodes/condition/schema.ts b/examples/workflow-builder-starter/src/nodes/condition/schema.ts new file mode 100644 index 000000000..7a16f6a0c --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/condition/schema.ts @@ -0,0 +1,13 @@ +import { sharedProperties, statusOptions } from '@workflowbuilder/sdk'; +import type { NodeSchema } from '@workflowbuilder/sdk'; + +export const schema = { + type: 'object', + properties: { + ...sharedProperties, + status: { type: 'string', options: Object.values(statusOptions) }, + condition: { type: 'string' }, + }, +} satisfies NodeSchema; + +export type ConditionNodeSchema = typeof schema; diff --git a/examples/workflow-builder-starter/src/nodes/condition/uischema.ts b/examples/workflow-builder-starter/src/nodes/condition/uischema.ts new file mode 100644 index 000000000..d70f477fc --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/condition/uischema.ts @@ -0,0 +1,30 @@ +import type { UISchema } from '@workflowbuilder/sdk'; +import { getScope, globalControls } from '@workflowbuilder/sdk'; + +import type { ConditionNodeSchema } from './schema'; + +const scope = getScope; + +const generalInformation: UISchema = { + type: 'Accordion', + label: 'General Information', + elements: [ + { type: 'Text', scope: scope('properties.label'), label: 'Title' }, + { type: 'Select', scope: scope('properties.status'), label: 'Status' }, + { type: 'Text', scope: scope('properties.description'), label: 'Description' }, + ], +}; + +export const uischema: UISchema = { + type: 'VerticalLayout', + elements: [ + ...globalControls, + generalInformation, + { + type: 'Text', + scope: scope('properties.condition'), + label: 'Condition', + placeholder: 'e.g. amount > 100', + }, + ], +}; diff --git a/examples/workflow-builder-starter/src/nodes/index.ts b/examples/workflow-builder-starter/src/nodes/index.ts new file mode 100644 index 000000000..10a0b8c65 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/index.ts @@ -0,0 +1,9 @@ +import type { PaletteItemOrGroup } from '@workflowbuilder/sdk'; + +import { action } from './action/action'; +import { condition } from './condition/condition'; +import { trigger } from './trigger/trigger'; + +// Passed to . Must stay a stable +// module-level reference - see the SDK docs on the `nodeTypes` prop. +export const nodeTypes: PaletteItemOrGroup[] = [trigger, action, condition]; diff --git a/examples/workflow-builder-starter/src/nodes/trigger/default-properties-data.ts b/examples/workflow-builder-starter/src/nodes/trigger/default-properties-data.ts new file mode 100644 index 000000000..b2deee78b --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/trigger/default-properties-data.ts @@ -0,0 +1,9 @@ +import type { NodeDataProperties } from '@workflowbuilder/sdk'; + +import type { TriggerNodeSchema } from './schema'; + +export const defaultPropertiesData: Required> = { + label: 'Trigger', + description: 'Starts the workflow', + status: 'active', +}; diff --git a/examples/workflow-builder-starter/src/nodes/trigger/schema.ts b/examples/workflow-builder-starter/src/nodes/trigger/schema.ts new file mode 100644 index 000000000..0264562b7 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/trigger/schema.ts @@ -0,0 +1,12 @@ +import { sharedProperties, statusOptions } from '@workflowbuilder/sdk'; +import type { NodeSchema } from '@workflowbuilder/sdk'; + +export const schema = { + type: 'object', + properties: { + ...sharedProperties, + status: { type: 'string', options: Object.values(statusOptions) }, + }, +} satisfies NodeSchema; + +export type TriggerNodeSchema = typeof schema; diff --git a/examples/workflow-builder-starter/src/nodes/trigger/trigger.ts b/examples/workflow-builder-starter/src/nodes/trigger/trigger.ts new file mode 100644 index 000000000..a430e7897 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/trigger/trigger.ts @@ -0,0 +1,15 @@ +import type { PaletteItem } from '@workflowbuilder/sdk'; + +import { defaultPropertiesData } from './default-properties-data'; +import { type TriggerNodeSchema, schema } from './schema'; +import { uischema } from './uischema'; + +export const trigger: PaletteItem = { + type: 'trigger', + icon: 'Lightning', + label: 'Trigger', + description: 'Starts the workflow', + defaultPropertiesData, + schema, + uischema, +}; diff --git a/examples/workflow-builder-starter/src/nodes/trigger/uischema.ts b/examples/workflow-builder-starter/src/nodes/trigger/uischema.ts new file mode 100644 index 000000000..95b97e147 --- /dev/null +++ b/examples/workflow-builder-starter/src/nodes/trigger/uischema.ts @@ -0,0 +1,21 @@ +import type { UISchema } from '@workflowbuilder/sdk'; +import { getScope, globalControls } from '@workflowbuilder/sdk'; + +import type { TriggerNodeSchema } from './schema'; + +const scope = getScope; + +const generalInformation: UISchema = { + type: 'Accordion', + label: 'General Information', + elements: [ + { type: 'Text', scope: scope('properties.label'), label: 'Title' }, + { type: 'Select', scope: scope('properties.status'), label: 'Status' }, + { type: 'Text', scope: scope('properties.description'), label: 'Description' }, + ], +}; + +export const uischema: UISchema = { + type: 'VerticalLayout', + elements: [...globalControls, generalInformation], +}; diff --git a/examples/workflow-builder-starter/src/vite-env.d.ts b/examples/workflow-builder-starter/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/examples/workflow-builder-starter/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/workflow-builder-starter/tsconfig.json b/examples/workflow-builder-starter/tsconfig.json new file mode 100644 index 000000000..791714c03 --- /dev/null +++ b/examples/workflow-builder-starter/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "include": ["src"] +} diff --git a/examples/workflow-builder-starter/vite.config.ts b/examples/workflow-builder-starter/vite.config.ts new file mode 100644 index 000000000..fabde1a8f --- /dev/null +++ b/examples/workflow-builder-starter/vite.config.ts @@ -0,0 +1,6 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react()], +}); From ec54c4586395a622bf8e21f77acc3a0d81c2dc68 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 14 Aug 2026 11:00:53 +0200 Subject: [PATCH 02/23] Add @workflowbuilder/ui + ui-tokens packages (1/3: packages) (#44) * feat(ui): add @workflowbuilder/ui and @workflowbuilder/ui-tokens packages * fix(ui): correctness fixes from review - SegmentPicker: type onChange/onSelect/onClick event as React.MouseEvent instead of MouseEventHandler (types vs runtime mismatch in public API) - Modal: apply className and ...rest to the same root element with ref, not split across the modal and content divs - Button: log a clear error for children that match no variant instead of silently rendering null - tokens: drop the swallowing try/catch in ejectTokens so a failed write aborts the build instead of feeding empty/stale JSON to the CSS step - tokens: lift tsconfig lib to es2021 (String.replaceAll) - add per-package lint-staged configs so in-package tsc runs on commit * refactor(ui): review polish pass - remove empty output.json and stale TODO in src/index.ts - rebrand vite lib + combine-css plugin names away from Overflow UI - Shape: replace '' sentinel with explicit 'default' - Snackbar/NodePanel JSDoc match actual behavior; check-built-css comment no longer references a non-existent stylelint rule - Switch: drop redundant styles prop (keep className) - Tooltip: inline the constant open/close delays, drop the speculative delay context (placement context kept) - tokens: scope codeChunks to generateCSSBundle; fix double-slash token dir * test(ui): add Vitest suites for ui + tokens pure logic Set up Vitest in @workflowbuilder/ui (jsdom) and @workflowbuilder/ui-tokens (node) and wire both into the root pnpm test. Extract the pure helpers that were buried in component files into sibling, non-barrel modules so they can be unit-tested without rendering: date-picker date-utils (parseDateValue/normalizeInitialValue/dayjsTokenToDateFns), and the menu/tooltip placement mappers. Public types are re-exported so the package API is unchanged. Coverage: button structural guards, rangeBetween, getValidShape, the date parsing/timezone logic, placement->side/align + offset mapping, and toFileName. 50 ui + 4 tokens tests. Exclude specs from the dts build so they don't ship. * chore(ui): release-prep for npm publish - narrow @base-ui/react peer range to ~1.4.0 (1.5/1.6 regressed transitions) - resolve the bundle-vs-dependency split: date-fns, react-day-picker, clsx and Phosphor are bundled into dist, so move them to devDependencies; only the external react-textarea-autosize stays a runtime dependency (peers unchanged) - rewrite npm description/keywords away from the diagram-library boilerplate - fix the README dev section (real pnpm --filter commands; no preview-page) and drop leftover 'Overflow UI' branding - correct CLAUDE.md: ui-tokens is private (not in changeset ignore) * ci(release): scoped per-package release tags + ui publish workflow Retire the single-package v* tag scheme now that the repo publishes two packages. Each package releases via its own scoped tag and workflow: - @workflowbuilder/sdk@X.Y.Z -> release-sdk.yml (trigger + version-parse updated) - @workflowbuilder/ui@X.Y.Z -> release-ui.yml (new; OIDC trusted publisher, provenance, build:ui, version/idempotency checks, GitHub Release) Add a UI lint/typecheck/test/build job to pr-check. Update RELEASE.md and CLAUDE.md for the scoped-tag two-package flow and correct the stale changeset ignore claim. Each package needs its own npm trusted publisher (manual, noted in RELEASE.md). * chore(ui): make @base-ui/react a regular dependency, not a peer ui is the sole consumer of Base UI and is version-locked to 1.4.x (later versions regressed transitions), so it should own that pinned version rather than delegate it to every consumer via a peer range. Move @base-ui/react to dependencies (catalog: -> 1.4.1 at publish); react / react-dom stay peers as true singletons. Consistent with react-textarea-autosize, the other external. * fix(ui): ship react-day-picker styles so the calendar renders react-day-picker v9 ships no styles of its own; the component never imported its stylesheet, so the calendar grid, month caption and nav rendered with browser defaults (vertical nav, bordered day buttons). Import react-day-picker/style.css so libInjectCss bundles it into dist (and the docs combined index.css), giving the calendar its real layout. * fix(ui): theme the date-picker calendar and add day hover react-day-picker ships a default blue accent and no hover state. Re-theme it to the design tokens: selected days get a filled --ax selected background instead of the accent ring, today uses a themed accent color, and day buttons gain a hover background (react-day-picker has none). New tokens: --ax-public-date-picker-date-{hover-background,selected,today}-color. * fix(ui): theme the date-picker month-nav chevrons react-day-picker paints the prev/next chevrons with --rdp-accent-color, which defaulted to a raw blue and clashed with the rest of the themed calendar. Paint them with a neutral nav token instead, and retheme --rdp-accent-color (focus rings, range endpoints) to the design-token accent so no raw blue remains. * fix(ui): IconSwitch uncontrolled icon swap + Accordion double toggle - IconSwitch picked the thumb icon from the controlled `checked` prop, so in uncontrolled mode the icon never swapped. Render both icons and swap them via the switch's `data-checked` state in CSS (works controlled + uncontrolled). - Accordion fired `onToggleOpen` twice when the chevron was clicked: the Collapsible button's toggle and the header's onClick both ran. The header is the single click target now (Collapsible is display-only, controlled via isExpanded); also fixed aria-expanded to reflect isExpanded. * docs(ui): correct and extend the CHANGELOG for the beta - @base-ui/react is a regular dependency now, not a peer (the entry still claimed peer); react / react-dom are the only peers. - document the review-driven breaking changes (Switch styles prop removed, Shape '' -> 'default', SegmentPicker event type, Modal prop target) and add a Fixed section (calendar styling, Accordion double-fire, IconSwitch, Button). * chore(ui): release as 2.0.0 with a fresh changelog First release under the @workflowbuilder/ui name, versioned 2.0.0 to sit on the Workflow Builder 2.x line. Rewrite CHANGELOG.md from scratch: a short note that the library moved from @synergycodes/overflow-ui (with a link to its old changelog) plus a single 2.0.0 entry covering the highlights and the migration deltas. Update the README technology note to match. * fix(ui): wrap remaining component CSS in cascade layers, guard against regressions Several component stylesheets (node ports, radio buttons, edge labels, snackbar, node icon/description, base typography) shipped rule bodies outside the ui.base/ui.component layers, letting unlayered CSS beat layered CSS regardless of specificity - the same bug class that shipped collapsed decision-node ports (WB-190). Extend check-built-css.ts with a second, independent check that fails the build if any dist CSS rule sits outside an @layer block, with documented exceptions for :root custom-property blocks, the deliberate react-day-picker override, and verbatim ui-tokens output. * chore(ui): Apache-2.0 license, externalize @phosphor-icons/react, export barrel gaps - License: Apache-2.0 (was MIT), LICENSE file copied from root and added to the published files array, publishConfig.access set to public - matches the packages/sdk precedent. - @phosphor-icons/react moves from devDependencies to a regular dependency and joins the Vite externalPackages list, so it's imported rather than bundled - same treatment as @base-ui/react. - Export CollapsibleProps and IconSwitchProps, which were already reachable through the barrel but not exported as types. - Extract tooltip's open/close delay constants and placement context into tooltip-context.ts so the barrel export of tooltip.tsx no longer leaks internals. - Fix a stale "Overflow UI" reference in css-layers.md. * chore(tokens): add lint/typecheck scripts, README, and CI coverage packages/tokens had no eslint config, no lint/typecheck scripts, and no README, and its checks weren't wired into pr-check.yml despite feeding packages/ui's build. Add the non-React eslint re-export used by packages/types and packages/execution-core, document the tokens.json -> Style Dictionary -> --ax-* CSS pipeline, and fold lint/typecheck/test into the existing UI CI job. * docs: document packages/tokens in CLAUDE.md workspace tables * chore(docs): remove orphaned apps/docs/src/generated/ui-api.json Stale generated artifact from an earlier TypeDoc setup; unreferenced anywhere in the repo and not produced by the current docs build. * chore: sync pnpm-lock.yaml after rebase onto main * fix(ui): require react 19 in peer range * refactor(ui): rewrite built-css layer check on postcss Replaces the hand-rolled CSS scanner with postcss (already a devDependency). Also tightens the :root exemption: a top-level :root block now passes only when every declaration is a custom property. * fix(ui): export WithIcon from the barrel so TypeDoc resolves the icon prop * fix(ui): pin the select caret to the trigger's far edge * refactor(tokens): derive token-set paths once from a validated manifest - config.ts names sets exactly as tokens.json exports them; buildManifest() validates the config up front and lists the available keys on mismatch - Style Dictionary runs with brokenReferences: 'throw' - a dangling token reference fails the build instead of shipping literal {token.path} values - dist/tokens.css inlines the primitives (no relative @import), so the file survives being copied out of the package alone * fix(ui): stamp the @layer order statement into every built stylesheet In the built barrel, libInjectCss evaluates per-chunk CSS before the entry CSS, so the order statement loaded last and the cascade inverted (ui.base beat ui.component) for every consumer following the README import path - secondary/ghost buttons rendered white-on-transparent. Duplicate statements are no-ops, so stamping the statement into each dist stylesheet makes any load order safe: barrel, subpath imports, and files copied out alone. combine-css-bundle now also throws when dist/assets is missing instead of silently skipping the published entrypoints. * fix(ui): layer react-day-picker styles under ui.base The calendar was the one component whose styles lived outside the layer contract: react-day-picker's stylesheet shipped unlayered and our theming had to stay unlayered too, winning by specificity. The stylesheet now joins ui.base at import time (@import ... layer(ui.base)) and the theming moved into @layer ui.component, winning by layer order alone. * fix(ui): inject box-sizing only into known styling contexts The plugin visited every rule, so @keyframes steps got box-sizing too and the declaration became part of the animation (animated values override normal author styles while running). Injection now allowlists plain styling contexts (media/supports/container/layer) and skips anything unknown - a missed redundant declaration is easy to spot, a wrongly injected one is not. vite-plugin-dts excludes root-level *.spec.mts: a spec escaping the filter drags vitest's type graph into the ts program and stalls the build. * feat(ui): extend the built-CSS guard with order, name, and surface checks Three new checks close the guard's blind spots: - every dist stylesheet must lead with the @layer order statement (the inverted-cascade class was invisible to the old checks) - only layer names declared in src/styles/layers.css may appear - a typo creates an undeclared layer that silently wins the cascade - every *.css entry in package.json exports must exist in dist, and no dist stylesheet may use @import (self-containment) The rdp- substring exemption is gone: react-day-picker styles are layered now, so the guard runs with no exceptions. Shared FailureReport type, all parsing through postcss. * fix(tokens): reject duplicate config sets and match primitives by basename Two latent traps from adversarial review: a set listed twice (or in both groups) made two builds race for one output file, and the theme filter matched primitive names as substrings of the whole source path - a set whose kebab name appears in the directory prefix (e.g. 'Tokens' vs ./dist/tokens/) would have silently emptied every theme. * fix(ui): close guard and plugin gaps found in adversarial review Guard (check-built-css.ts): - allowlist matches exact dist paths, not basenames - a component entry named 'tokens' no longer inherits the token files' exemption - exports check walks conditional-export objects, not just string targets - layers.css is parsed for the @layer statement with a clear error instead of an unchecked .first cast that crashed on a leading comment - comments inside :root token blocks are no longer flagged Plugin (postcss-box-sizing.mts): - ancestor walk is properly typed (Document in the parent chain surfaced TS2322 under strict) and the :root skip covers grouped/qualified selectors - dropped the inert vendor-prefix stripper; spec grew to 10 cases tsconfig now includes the root build .mts files and scripts/ so these files leave the typecheck blind spot; specs and vite.config.mts stay out - with allowImportingTsExtensions (needed for the config's .mts imports) or a vitest-importing spec in the program, tsc stalls for minutes. * docs(ui): record the box-sizing plugin decision Decision log for keeping build-time per-rule injection over lint-based and selector-based alternatives (box-sizing does not inherit; portals escape subtree selectors; a global reset leaks onto consumer elements). css-layers.md points at the mechanism; CHANGELOG no longer claims styles.css establishes the layer order - every stylesheet carries it now. * fix(ui): focus the field when clicking input and text-area wrapper padding The padding sits on the wrapper, not the form element, so clicks in that area never focused the field. Forward pointerdown to the element; adornment clicks keep their own behavior. * fix(ui): soften the modal exit with symmetric ease timing ease-out on the exit transition drops most opacity in the first frames, which reads as an abrupt vanish; the pre-migration modal used symmetric ease. Applied to popup and backdrop in both directions. * fix(ui): split build tooling into its own ts program Extending tsconfig.json's include with root build files made every tool that reads the package tsconfig (TypeDoc in the docs generator, and vite-plugin-dts before it) pull them into its program - TypeDoc hung indefinitely. The component program is src-only again; build files and scripts typecheck through tsconfig.node.json, chained in pnpm typecheck. * fix(ui): keep react-flow handle state rules content-box The build injects border-box per rule, so hover/connecting states of the opted-out handle silently shrank from 1rem to 0.75rem. State rules now repeat the opt-out; the plugin spec documents the contract. * fix(tokens): fail the build when set keys collide after file-name normalization * fix(tokens): clean dist before building so renamed sets leave no stale output * fix(ui): fail the combine step when dist/assets holds no component CSS * feat(ui): ship variable defaults inside the ui.base layer Unlayered :root defaults raced consumer overrides by load order: a lazily loaded component stylesheet silently restored the default. The build now wraps top-level :root blocks in @layer ui.base (new postcss plugin for component CSS, copy transform for the generated token files), so any unlayered consumer override wins by layer rules. check-built-css drops its :root and token-file exemptions and also rejects var() calls whose first argument is not a dashed ident (var(), var(no-dashes)). * feat(ui): widen react peer range to ^18.0.0 || ^19.0.0 Base UI itself supports 17-19 and no React-19-only API is used; the ^19 peer was declared from the build target, not an actual requirement. * fix(ui): close the modal on a backdrop click Dialog.Popup carried a full-screen wrapper, so every click landed inside the popup and Base UI never saw an outside press - the modal could only be dismissed with the close button or Esc. The popup is now the dialog window itself (centered, capped at the viewport with its own scrolling content area), and the orphan .backdrop-close class is gone. * fix(ui): raise the date-picker popover like select and menu Its Positioner was the only one without the shared popup class, so a calendar opened inside a modal rendered behind it - which the SDK papered over with a global z-index override. The 4px sideOffset goes away with it: the shared class already carries that gap. * fix(ui): raise the modal backdrop above host panels At z-index 0 the backdrop sat below the consuming app's own panels, so they stayed undimmed while a dialog was open and absorbed the clicks that should have dismissed it. * refactor(ui): trim inline comments to the traps worth keeping Cut prose that paraphrases the code or records why a change was made - that belongs in the commit and the PR. What stays is the handful of non-obvious constraints: react-day-picker's selection quirks, the spec shape that stalls the dts build, the cascade rule the state selectors depend on. * fix(ui): stop the text area overriding its typography class The `font` shorthand set the size too, so a text area given ax-public-p10 rendered at the inherited 16px instead of 12px - and the taller field pushed the properties panel into scrolling. Only the family is inherited now; size, weight and line height come from the typography class. --------- Co-authored-by: Jan Librowski Co-authored-by: librowski-synergy Co-authored-by: librowski-synergy --- .changeset/ui-layer-root-defaults.md | 5 + .changeset/ui-react-18-peer.md | 5 + .github/workflows/pr-check.yml | 55 +- .github/workflows/release-sdk.yml | 18 +- .github/workflows/release-ui.yml | 114 + CLAUDE.md | 71 +- knip.config.js | 11 + package.json | 5 +- packages/sdk/RELEASE.md | 36 +- packages/tokens/README.md | 47 + packages/tokens/config.ts | 17 + packages/tokens/eslint.config.mjs | 1 + packages/tokens/lint-staged.config.mjs | 1 + packages/tokens/package.json | 23 + packages/tokens/src/constants.ts | 2 + packages/tokens/src/eject-tokens.ts | 21 + packages/tokens/src/generate-css-bundle.ts | 17 + packages/tokens/src/index.ts | 18 + packages/tokens/src/manifest.spec.ts | 63 + packages/tokens/src/manifest.ts | 71 + packages/tokens/src/to-file-name.spec.ts | 21 + packages/tokens/src/to-file-name.ts | 8 + packages/tokens/src/tokens-to-css.ts | 88 + packages/tokens/src/types.ts | 27 + packages/tokens/tokens.json | 3408 +++++++++++++++++ packages/tokens/tsconfig.json | 8 + packages/tokens/vitest.config.mts | 7 + packages/ui/CHANGELOG.md | 64 + packages/ui/LICENSE | 201 + packages/ui/README.md | 164 + packages/ui/combine-css-bundle.mts | 96 + packages/ui/css-layers.md | 63 + packages/ui/eslint.config.mjs | 44 + packages/ui/lint-staged.config.mjs | 1 + packages/ui/package.json | 98 + .../ui/postcss-box-sizing.decision-log.md | 64 + packages/ui/postcss-box-sizing.mts | 72 + packages/ui/postcss-box-sizing.spec.mts | 76 + packages/ui/postcss-layer-root-defaults.mts | 42 + .../ui/postcss-layer-root-defaults.spec.mts | 39 + packages/ui/scripts/check-built-css.ts | 299 ++ .../components/accordion/accordion.module.css | 23 + .../ui/src/components/accordion/accordion.tsx | 79 + packages/ui/src/components/accordion/index.ts | 1 + .../src/components/avatar/avatar.module.css | 43 + packages/ui/src/components/avatar/avatar.tsx | 31 + packages/ui/src/components/avatar/index.ts | 1 + .../button/base-button/base-button.module.css | 34 + .../button/base-button/base-button.tsx | 38 + .../ui/src/components/button/guards.spec.ts | 85 + packages/ui/src/components/button/guards.ts | 18 + packages/ui/src/components/button/index.ts | 3 + .../button/nav-button/nav-button.tsx | 111 + .../nav-button-icon-padding.module.css | 14 + .../nav-icon-button.module.css | 11 + .../nav-icon-button/nav-icon-button.tsx | 40 + .../nav-button-gap.module.css | 15 + .../nav-icon-label-button-padding.module.css | 16 + .../nav-icon-label-button.tsx | 40 + .../nav-label-button-padding.module.css | 16 + .../nav-label-button/nav-label-button.tsx | 26 + .../nav-button-border-radius.module.css | 16 + .../styles/nav-button-font-size.module.css | 6 + .../styles/nav-button-icon-size.module.css | 20 + .../nav-button/styles/nav-button.module.css | 37 + .../src/components/button/nav-button/types.ts | 8 + .../button/regular-button/button.tsx | 108 + .../icon-button/icon-button.tsx | 27 + .../icon-label-button/icon-label-button.tsx | 27 + .../label-button/label-button.tsx | 29 + .../label-button/loader.module.css | 58 + .../components/button/regular-button/types.ts | 24 + .../button/styles/border-radius.module.css | 37 + .../button/styles/font-size.module.css | 15 + .../components/button/styles/gap.module.css | 29 + .../icon-label-button-padding.module.css | 34 + .../button/styles/icon-padding.module.css | 29 + .../button/styles/icon-size.module.css | 44 + .../styles/label-button-padding.module.css | 31 + .../button/styles/variant.module.css | 180 + packages/ui/src/components/button/types.ts | 10 + .../components/checkbox/checkbox.module.css | 130 + .../ui/src/components/checkbox/checkbox.tsx | 50 + packages/ui/src/components/checkbox/index.ts | 1 + .../collapsible/collapsible.module.css | 35 + .../components/collapsible/collapsible.tsx | 101 + .../ui/src/components/collapsible/index.ts | 1 + .../date-picker/date-picker.module.css | 72 + .../components/date-picker/date-picker.tsx | 290 ++ .../components/date-picker/date-utils.spec.ts | 83 + .../src/components/date-picker/date-utils.ts | 50 + .../ui/src/components/date-picker/index.ts | 2 + .../ui/src/components/date-picker/types.ts | 80 + .../src/components/date-picker/variables.css | 20 + .../edge-label/edge-label-size.module.css | 101 + .../edge/edge-label/edge-label.module.css | 61 + .../components/edge/edge-label/edge-label.tsx | 65 + packages/ui/src/components/edge/index.ts | 3 + packages/ui/src/components/edge/types.ts | 7 + .../edge/use-edge-styles/use-edge-styles.css | 8 + .../edge/use-edge-styles/use-edge-styles.tsx | 48 + packages/ui/src/components/input/index.ts | 2 + .../components/input/input-root.module.css | 35 + .../ui/src/components/input/input.module.css | 26 + packages/ui/src/components/input/input.tsx | 43 + packages/ui/src/components/input/types.ts | 25 + .../ui/src/components/input/variables.css | 14 + packages/ui/src/components/menu/index.ts | 2 + packages/ui/src/components/menu/menu-item.tsx | 22 + packages/ui/src/components/menu/menu.tsx | 93 + .../ui/src/components/menu/placement.spec.ts | 44 + packages/ui/src/components/menu/placement.ts | 39 + packages/ui/src/components/menu/types.ts | 8 + packages/ui/src/components/modal/index.ts | 2 + .../ui/src/components/modal/modal.module.css | 203 + packages/ui/src/components/modal/modal.tsx | 112 + packages/ui/src/components/modal/types.ts | 1 + packages/ui/src/components/node/index.ts | 4 + .../node-as-port-wrapper.tsx | 61 + .../node-as-port-wrapper/node-as-port.css | 10 + .../node-description.module.css | 27 + .../node-description/node-description.tsx | 18 + .../node/node-icon/node-icon.module.css | 23 + .../components/node/node-icon/node-icon.tsx | 13 + .../node/node-panel/handle.module.css | 104 + .../node/node-panel/node-panel.module.css | 74 + .../components/node/node-panel/node-panel.tsx | 139 + .../ui/src/components/radio-button/index.ts | 1 + .../radio-button/radio-size.module.css | 41 + .../components/radio-button/radio.module.css | 74 + .../ui/src/components/radio-button/radio.tsx | 42 + .../border-radius-size.module.css | 39 + .../ui/src/components/segment-picker/index.ts | 1 + .../item/segment-picker-item-shape.module.css | 5 + .../item/segment-picker-item.tsx | 68 + .../segment-picker/segment-picker.module.css | 25 + .../segment-picker/segment-picker.tsx | 79 + .../segment-picker/utils/context.ts | 12 + .../utils/get-valid-shape.spec.ts | 40 + .../segment-picker/utils/get-valid-shape.ts | 22 + packages/ui/src/components/select/index.ts | 2 + .../select-button/select-button.module.css | 60 + .../select/select-button/select-button.tsx | 14 + .../select/select-option/select-option.tsx | 26 + .../select-value/select-value.module.css | 20 + .../select/select-value/select-value.tsx | 29 + .../src/components/select/select.module.css | 11 + packages/ui/src/components/select/select.tsx | 128 + packages/ui/src/components/select/types.ts | 6 + packages/ui/src/components/separator/index.ts | 1 + .../components/separator/separator.module.css | 16 + .../ui/src/components/separator/separator.tsx | 8 + .../components/action-buttons.module.css | 8 + .../snackbar/components/action-buttons.tsx | 43 + .../snackbar/components/icon.module.css | 33 + .../components/snackbar/components/icon.tsx | 24 + .../snackbar/components/message.module.css | 18 + .../snackbar/components/message.tsx | 17 + packages/ui/src/components/snackbar/index.ts | 2 + .../components/snackbar/snackbar.module.css | 81 + .../ui/src/components/snackbar/snackbar.tsx | 64 + packages/ui/src/components/snackbar/types.ts | 9 + packages/ui/src/components/status/index.ts | 1 + .../src/components/status/status.module.css | 33 + packages/ui/src/components/status/status.tsx | 32 + .../switch/icon-switch/icon-switch.module.css | 106 + .../switch/icon-switch/icon-switch.tsx | 49 + packages/ui/src/components/switch/index.ts | 2 + .../components/switch/switch-size.module.css | 97 + .../src/components/switch/switch.module.css | 78 + packages/ui/src/components/switch/switch.tsx | 82 + packages/ui/src/components/text-area/index.ts | 1 + .../components/text-area/text-area.module.css | 64 + .../ui/src/components/text-area/text-area.tsx | 124 + packages/ui/src/components/tooltip/index.ts | 4 + .../src/components/tooltip/placement.spec.ts | 15 + .../ui/src/components/tooltip/placement.ts | 17 + .../components/tooltip/tooltip-content.tsx | 48 + .../src/components/tooltip/tooltip-context.ts | 17 + .../components/tooltip/tooltip-trigger.tsx | 45 + .../src/components/tooltip/tooltip.module.css | 35 + .../ui/src/components/tooltip/tooltip.tsx | 69 + packages/ui/src/components/tooltip/types.ts | 1 + packages/ui/src/index.ts | 29 + .../src/shared/hooks/use-transition-event.ts | 26 + .../shared/styles/input-font-size.module.css | 10 + .../src/shared/styles/input-size.module.css | 33 + .../ui/src/shared/styles/list-box.module.css | 49 + .../shared/styles/list-item-size.module.css | 31 + .../ui/src/shared/styles/list-item.module.css | 69 + packages/ui/src/shared/types/item-size.ts | 3 + packages/ui/src/shared/types/list-item.ts | 7 + packages/ui/src/shared/types/selector-size.ts | 3 + packages/ui/src/shared/types/size.ts | 2 + packages/ui/src/shared/types/with-icon.ts | 6 + packages/ui/src/shared/utils/arrays.spec.ts | 26 + packages/ui/src/shared/utils/arrays.ts | 6 + packages/ui/src/styles/globals.css | 7 + packages/ui/src/styles/layers.css | 1 + packages/ui/src/styles/typography.css | 169 + packages/ui/tsconfig.json | 13 + packages/ui/tsconfig.node.json | 14 + packages/ui/types.d.ts | 1 + packages/ui/vite.config.mts | 143 + packages/ui/vitest.config.mts | 18 + pnpm-lock.yaml | 1178 +++++- pnpm-workspace.yaml | 5 + 207 files changed, 12946 insertions(+), 149 deletions(-) create mode 100644 .changeset/ui-layer-root-defaults.md create mode 100644 .changeset/ui-react-18-peer.md create mode 100644 .github/workflows/release-ui.yml create mode 100644 packages/tokens/README.md create mode 100644 packages/tokens/config.ts create mode 100644 packages/tokens/eslint.config.mjs create mode 100644 packages/tokens/lint-staged.config.mjs create mode 100644 packages/tokens/package.json create mode 100644 packages/tokens/src/constants.ts create mode 100644 packages/tokens/src/eject-tokens.ts create mode 100644 packages/tokens/src/generate-css-bundle.ts create mode 100644 packages/tokens/src/index.ts create mode 100644 packages/tokens/src/manifest.spec.ts create mode 100644 packages/tokens/src/manifest.ts create mode 100644 packages/tokens/src/to-file-name.spec.ts create mode 100644 packages/tokens/src/to-file-name.ts create mode 100644 packages/tokens/src/tokens-to-css.ts create mode 100644 packages/tokens/src/types.ts create mode 100644 packages/tokens/tokens.json create mode 100644 packages/tokens/tsconfig.json create mode 100644 packages/tokens/vitest.config.mts create mode 100644 packages/ui/CHANGELOG.md create mode 100644 packages/ui/LICENSE create mode 100644 packages/ui/README.md create mode 100644 packages/ui/combine-css-bundle.mts create mode 100644 packages/ui/css-layers.md create mode 100644 packages/ui/eslint.config.mjs create mode 100644 packages/ui/lint-staged.config.mjs create mode 100644 packages/ui/package.json create mode 100644 packages/ui/postcss-box-sizing.decision-log.md create mode 100644 packages/ui/postcss-box-sizing.mts create mode 100644 packages/ui/postcss-box-sizing.spec.mts create mode 100644 packages/ui/postcss-layer-root-defaults.mts create mode 100644 packages/ui/postcss-layer-root-defaults.spec.mts create mode 100644 packages/ui/scripts/check-built-css.ts create mode 100644 packages/ui/src/components/accordion/accordion.module.css create mode 100644 packages/ui/src/components/accordion/accordion.tsx create mode 100644 packages/ui/src/components/accordion/index.ts create mode 100644 packages/ui/src/components/avatar/avatar.module.css create mode 100644 packages/ui/src/components/avatar/avatar.tsx create mode 100644 packages/ui/src/components/avatar/index.ts create mode 100644 packages/ui/src/components/button/base-button/base-button.module.css create mode 100644 packages/ui/src/components/button/base-button/base-button.tsx create mode 100644 packages/ui/src/components/button/guards.spec.ts create mode 100644 packages/ui/src/components/button/guards.ts create mode 100644 packages/ui/src/components/button/index.ts create mode 100644 packages/ui/src/components/button/nav-button/nav-button.tsx create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-button/nav-button-icon-padding.module.css create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.module.css create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.tsx create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-button-gap.module.css create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button-padding.module.css create mode 100644 packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button.tsx create mode 100644 packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button-padding.module.css create mode 100644 packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button.tsx create mode 100644 packages/ui/src/components/button/nav-button/styles/nav-button-border-radius.module.css create mode 100644 packages/ui/src/components/button/nav-button/styles/nav-button-font-size.module.css create mode 100644 packages/ui/src/components/button/nav-button/styles/nav-button-icon-size.module.css create mode 100644 packages/ui/src/components/button/nav-button/styles/nav-button.module.css create mode 100644 packages/ui/src/components/button/nav-button/types.ts create mode 100644 packages/ui/src/components/button/regular-button/button.tsx create mode 100644 packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx create mode 100644 packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx create mode 100644 packages/ui/src/components/button/regular-button/label-button/label-button.tsx create mode 100644 packages/ui/src/components/button/regular-button/label-button/loader.module.css create mode 100644 packages/ui/src/components/button/regular-button/types.ts create mode 100644 packages/ui/src/components/button/styles/border-radius.module.css create mode 100644 packages/ui/src/components/button/styles/font-size.module.css create mode 100644 packages/ui/src/components/button/styles/gap.module.css create mode 100644 packages/ui/src/components/button/styles/icon-label-button-padding.module.css create mode 100644 packages/ui/src/components/button/styles/icon-padding.module.css create mode 100644 packages/ui/src/components/button/styles/icon-size.module.css create mode 100644 packages/ui/src/components/button/styles/label-button-padding.module.css create mode 100644 packages/ui/src/components/button/styles/variant.module.css create mode 100644 packages/ui/src/components/button/types.ts create mode 100644 packages/ui/src/components/checkbox/checkbox.module.css create mode 100644 packages/ui/src/components/checkbox/checkbox.tsx create mode 100644 packages/ui/src/components/checkbox/index.ts create mode 100644 packages/ui/src/components/collapsible/collapsible.module.css create mode 100644 packages/ui/src/components/collapsible/collapsible.tsx create mode 100644 packages/ui/src/components/collapsible/index.ts create mode 100644 packages/ui/src/components/date-picker/date-picker.module.css create mode 100644 packages/ui/src/components/date-picker/date-picker.tsx create mode 100644 packages/ui/src/components/date-picker/date-utils.spec.ts create mode 100644 packages/ui/src/components/date-picker/date-utils.ts create mode 100644 packages/ui/src/components/date-picker/index.ts create mode 100644 packages/ui/src/components/date-picker/types.ts create mode 100644 packages/ui/src/components/date-picker/variables.css create mode 100644 packages/ui/src/components/edge/edge-label/edge-label-size.module.css create mode 100644 packages/ui/src/components/edge/edge-label/edge-label.module.css create mode 100644 packages/ui/src/components/edge/edge-label/edge-label.tsx create mode 100644 packages/ui/src/components/edge/index.ts create mode 100644 packages/ui/src/components/edge/types.ts create mode 100644 packages/ui/src/components/edge/use-edge-styles/use-edge-styles.css create mode 100644 packages/ui/src/components/edge/use-edge-styles/use-edge-styles.tsx create mode 100644 packages/ui/src/components/input/index.ts create mode 100644 packages/ui/src/components/input/input-root.module.css create mode 100644 packages/ui/src/components/input/input.module.css create mode 100644 packages/ui/src/components/input/input.tsx create mode 100644 packages/ui/src/components/input/types.ts create mode 100644 packages/ui/src/components/input/variables.css create mode 100644 packages/ui/src/components/menu/index.ts create mode 100644 packages/ui/src/components/menu/menu-item.tsx create mode 100644 packages/ui/src/components/menu/menu.tsx create mode 100644 packages/ui/src/components/menu/placement.spec.ts create mode 100644 packages/ui/src/components/menu/placement.ts create mode 100644 packages/ui/src/components/menu/types.ts create mode 100644 packages/ui/src/components/modal/index.ts create mode 100644 packages/ui/src/components/modal/modal.module.css create mode 100644 packages/ui/src/components/modal/modal.tsx create mode 100644 packages/ui/src/components/modal/types.ts create mode 100644 packages/ui/src/components/node/index.ts create mode 100644 packages/ui/src/components/node/node-as-port-wrapper/node-as-port-wrapper.tsx create mode 100644 packages/ui/src/components/node/node-as-port-wrapper/node-as-port.css create mode 100644 packages/ui/src/components/node/node-description/node-description.module.css create mode 100644 packages/ui/src/components/node/node-description/node-description.tsx create mode 100644 packages/ui/src/components/node/node-icon/node-icon.module.css create mode 100644 packages/ui/src/components/node/node-icon/node-icon.tsx create mode 100644 packages/ui/src/components/node/node-panel/handle.module.css create mode 100644 packages/ui/src/components/node/node-panel/node-panel.module.css create mode 100644 packages/ui/src/components/node/node-panel/node-panel.tsx create mode 100644 packages/ui/src/components/radio-button/index.ts create mode 100644 packages/ui/src/components/radio-button/radio-size.module.css create mode 100644 packages/ui/src/components/radio-button/radio.module.css create mode 100644 packages/ui/src/components/radio-button/radio.tsx create mode 100644 packages/ui/src/components/segment-picker/border-radius-size.module.css create mode 100644 packages/ui/src/components/segment-picker/index.ts create mode 100644 packages/ui/src/components/segment-picker/item/segment-picker-item-shape.module.css create mode 100644 packages/ui/src/components/segment-picker/item/segment-picker-item.tsx create mode 100644 packages/ui/src/components/segment-picker/segment-picker.module.css create mode 100644 packages/ui/src/components/segment-picker/segment-picker.tsx create mode 100644 packages/ui/src/components/segment-picker/utils/context.ts create mode 100644 packages/ui/src/components/segment-picker/utils/get-valid-shape.spec.ts create mode 100644 packages/ui/src/components/segment-picker/utils/get-valid-shape.ts create mode 100644 packages/ui/src/components/select/index.ts create mode 100644 packages/ui/src/components/select/select-button/select-button.module.css create mode 100644 packages/ui/src/components/select/select-button/select-button.tsx create mode 100644 packages/ui/src/components/select/select-option/select-option.tsx create mode 100644 packages/ui/src/components/select/select-value/select-value.module.css create mode 100644 packages/ui/src/components/select/select-value/select-value.tsx create mode 100644 packages/ui/src/components/select/select.module.css create mode 100644 packages/ui/src/components/select/select.tsx create mode 100644 packages/ui/src/components/select/types.ts create mode 100644 packages/ui/src/components/separator/index.ts create mode 100644 packages/ui/src/components/separator/separator.module.css create mode 100644 packages/ui/src/components/separator/separator.tsx create mode 100644 packages/ui/src/components/snackbar/components/action-buttons.module.css create mode 100644 packages/ui/src/components/snackbar/components/action-buttons.tsx create mode 100644 packages/ui/src/components/snackbar/components/icon.module.css create mode 100644 packages/ui/src/components/snackbar/components/icon.tsx create mode 100644 packages/ui/src/components/snackbar/components/message.module.css create mode 100644 packages/ui/src/components/snackbar/components/message.tsx create mode 100644 packages/ui/src/components/snackbar/index.ts create mode 100644 packages/ui/src/components/snackbar/snackbar.module.css create mode 100644 packages/ui/src/components/snackbar/snackbar.tsx create mode 100644 packages/ui/src/components/snackbar/types.ts create mode 100644 packages/ui/src/components/status/index.ts create mode 100644 packages/ui/src/components/status/status.module.css create mode 100644 packages/ui/src/components/status/status.tsx create mode 100644 packages/ui/src/components/switch/icon-switch/icon-switch.module.css create mode 100644 packages/ui/src/components/switch/icon-switch/icon-switch.tsx create mode 100644 packages/ui/src/components/switch/index.ts create mode 100644 packages/ui/src/components/switch/switch-size.module.css create mode 100644 packages/ui/src/components/switch/switch.module.css create mode 100644 packages/ui/src/components/switch/switch.tsx create mode 100644 packages/ui/src/components/text-area/index.ts create mode 100644 packages/ui/src/components/text-area/text-area.module.css create mode 100644 packages/ui/src/components/text-area/text-area.tsx create mode 100644 packages/ui/src/components/tooltip/index.ts create mode 100644 packages/ui/src/components/tooltip/placement.spec.ts create mode 100644 packages/ui/src/components/tooltip/placement.ts create mode 100644 packages/ui/src/components/tooltip/tooltip-content.tsx create mode 100644 packages/ui/src/components/tooltip/tooltip-context.ts create mode 100644 packages/ui/src/components/tooltip/tooltip-trigger.tsx create mode 100644 packages/ui/src/components/tooltip/tooltip.module.css create mode 100644 packages/ui/src/components/tooltip/tooltip.tsx create mode 100644 packages/ui/src/components/tooltip/types.ts create mode 100644 packages/ui/src/index.ts create mode 100644 packages/ui/src/shared/hooks/use-transition-event.ts create mode 100644 packages/ui/src/shared/styles/input-font-size.module.css create mode 100644 packages/ui/src/shared/styles/input-size.module.css create mode 100644 packages/ui/src/shared/styles/list-box.module.css create mode 100644 packages/ui/src/shared/styles/list-item-size.module.css create mode 100644 packages/ui/src/shared/styles/list-item.module.css create mode 100644 packages/ui/src/shared/types/item-size.ts create mode 100644 packages/ui/src/shared/types/list-item.ts create mode 100644 packages/ui/src/shared/types/selector-size.ts create mode 100644 packages/ui/src/shared/types/size.ts create mode 100644 packages/ui/src/shared/types/with-icon.ts create mode 100644 packages/ui/src/shared/utils/arrays.spec.ts create mode 100644 packages/ui/src/shared/utils/arrays.ts create mode 100644 packages/ui/src/styles/globals.css create mode 100644 packages/ui/src/styles/layers.css create mode 100644 packages/ui/src/styles/typography.css create mode 100644 packages/ui/tsconfig.json create mode 100644 packages/ui/tsconfig.node.json create mode 100644 packages/ui/types.d.ts create mode 100644 packages/ui/vite.config.mts create mode 100644 packages/ui/vitest.config.mts diff --git a/.changeset/ui-layer-root-defaults.md b/.changeset/ui-layer-root-defaults.md new file mode 100644 index 000000000..e51b1f16b --- /dev/null +++ b/.changeset/ui-layer-root-defaults.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +Variable defaults (`--ax-public-*` component defaults and the `--ax-*` design tokens in `tokens.css`) now ship inside the `ui.base` cascade layer. A plain `:root { --ax-…: … }` override in your app now wins regardless of stylesheet load order; previously a lazily loaded component stylesheet could silently restore the default. diff --git a/.changeset/ui-react-18-peer.md b/.changeset/ui-react-18-peer.md new file mode 100644 index 000000000..908ca2778 --- /dev/null +++ b/.changeset/ui-react-18-peer.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +React 18 is accepted again: the `react` / `react-dom` peer ranges widened from `^19.0.0` to `^18.0.0 || ^19.0.0`, matching Base UI's own support range. diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 104f7e3d5..844b4d0df 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,10 +1,11 @@ name: PR Check # Runs on every PR to give reviewers fast feedback on whether the change -# is safe to merge. Focus on @workflowbuilder/sdk (what we publish to -# npm) + global format consistency. Other workspaces (apps/docs, demo, -# ai-studio, …) are not checked here — they're internal and have their -# own broken-state tolerances (e.g. starlight virtual-module type errors). +# is safe to merge. Focus on the published packages @workflowbuilder/sdk and +# @workflowbuilder/ui (plus its private @workflowbuilder/ui-tokens build) + +# global format consistency. Other workspaces (apps/docs, demo, ai-studio, …) +# are not checked here — they're internal and have their own broken-state +# tolerances (e.g. starlight virtual-module type errors). on: pull_request: @@ -98,3 +99,49 @@ jobs: - name: Build run: npm run build + + ui: + name: UI + UI tokens lint + typecheck + test + build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Enable Corepack + run: npm i -g corepack@latest + + - name: Install pnpm + run: corepack prepare + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint tokens + run: pnpm --filter @workflowbuilder/ui-tokens lint + + - name: Typecheck tokens + run: pnpm --filter @workflowbuilder/ui-tokens typecheck + + - name: Test tokens + run: pnpm --filter @workflowbuilder/ui-tokens test + + - name: Lint + run: pnpm --filter @workflowbuilder/ui lint + + - name: Typecheck + run: pnpm --filter @workflowbuilder/ui typecheck + + - name: Test + run: pnpm --filter @workflowbuilder/ui test + + - name: Build + # build:ui builds @workflowbuilder/ui-tokens first, then ui, then runs + # the check:built-css guard. + run: pnpm build:ui diff --git a/.github/workflows/release-sdk.yml b/.github/workflows/release-sdk.yml index 4c4858410..b3cf680f3 100644 --- a/.github/workflows/release-sdk.yml +++ b/.github/workflows/release-sdk.yml @@ -1,13 +1,15 @@ name: Release SDK -# Triggers on tags `vX.Y.Z` matching the version in packages/sdk/package.json. -# Maintainer pushes the tag after merging the version-bump PR (which ran -# `pnpm changeset version`). See packages/sdk/RELEASE.md. +# Triggers on scoped tags `@workflowbuilder/sdk@X.Y.Z` matching the version in +# packages/sdk/package.json. Scoped tags let this repo publish more than one +# package (see release-ui.yml for @workflowbuilder/ui). Maintainer pushes the +# tag after merging the version-bump PR (which ran `pnpm changeset version`). +# See packages/sdk/RELEASE.md. on: push: tags: - - "v*" + - "@workflowbuilder/sdk@*" workflow_dispatch: # manual fire from GitHub UI — for testing or rerunning a failed publish permissions: @@ -62,10 +64,12 @@ jobs: run: pnpm --filter @workflowbuilder/sdk build:lib - name: Verify version matches tag - # Tag refs/tags/vX.Y.Z must match packages/sdk/package.json version. - # Catches push of wrong tag (typo, pushed before version-bump PR merged). + # Tag `@workflowbuilder/sdk@X.Y.Z` must match packages/sdk/package.json + # version. ${GITHUB_REF_NAME##*@} strips everything up to the last `@`, + # leaving X.Y.Z. Catches push of wrong tag (typo, pushed before the + # version-bump PR merged). run: | - TAG_VERSION="${GITHUB_REF_NAME#v}" + TAG_VERSION="${GITHUB_REF_NAME##*@}" PKG_VERSION=$(node -p "require('./packages/sdk/package.json').version") if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)." diff --git a/.github/workflows/release-ui.yml b/.github/workflows/release-ui.yml new file mode 100644 index 000000000..06b7b3ee5 --- /dev/null +++ b/.github/workflows/release-ui.yml @@ -0,0 +1,114 @@ +name: Release UI + +# Triggers on scoped tags `@workflowbuilder/ui@X.Y.Z` matching the version in +# packages/ui/package.json. Mirrors release-sdk.yml. Maintainer pushes the tag +# after merging the version-bump PR (which ran `pnpm changeset version`). See +# packages/sdk/RELEASE.md (the release flow is shared between both packages). +# +# One-time npm setup: @workflowbuilder/ui needs its own GitHub Actions trusted +# publisher registered on npmjs.com pointing at THIS workflow file +# (.github/workflows/release-ui.yml). It is separate from the sdk one. + +on: + push: + tags: + - "@workflowbuilder/ui@*" + workflow_dispatch: # manual fire from GitHub UI — for testing or rerunning a failed publish + +permissions: + contents: write # create GitHub Release + id-token: write # OIDC for npm Trusted Publisher (and provenance attestation) + +jobs: + publish: + name: Build and publish to npm + runs-on: ubuntu-latest + steps: + - name: Checkout code (at tag) + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 # full history so changesets can read CHANGELOG context + + - name: Set up Node.js + # No `registry-url:` here on purpose — see the note in release-sdk.yml. + # setup-node's registry-url breaks OIDC by injecting a sentinel token. + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Enable Corepack + run: npm i -g corepack@latest + + - name: Install pnpm + run: corepack prepare + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # Defensive layer — fail before npm, not on consumers. + - name: Lint + run: pnpm --filter @workflowbuilder/ui lint + + - name: Typecheck + run: pnpm --filter @workflowbuilder/ui typecheck + + - name: Test + run: pnpm --filter @workflowbuilder/ui test + + - name: Build UI + # build:ui builds @workflowbuilder/ui-tokens first (ui consumes its + # built tokens.css) and runs the check:built-css guard afterwards. + run: pnpm build:ui + + - name: Verify version matches tag + # Tag `@workflowbuilder/ui@X.Y.Z` must match packages/ui/package.json + # version. ${GITHUB_REF_NAME##*@} strips everything up to the last `@`. + run: | + TAG_VERSION="${GITHUB_REF_NAME##*@}" + PKG_VERSION=$(node -p "require('./packages/ui/package.json').version") + if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then + echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)." + echo "Did you forget to merge the version-bump PR before pushing the tag?" + exit 1 + fi + echo "Publishing @workflowbuilder/ui@$PKG_VERSION" + + - name: Check if version already published + # Idempotency — re-pushing a tag should not re-publish. + id: check-version + run: | + PKG_VERSION=$(node -p "require('./packages/ui/package.json').version") + if npm view "@workflowbuilder/ui@$PKG_VERSION" version 2>/dev/null; then + echo "already_published=true" >> "$GITHUB_OUTPUT" + echo "::notice::@workflowbuilder/ui@$PKG_VERSION already on npm — skipping publish step." + else + echo "already_published=false" >> "$GITHUB_OUTPUT" + fi + + - name: Publish to npm + # Authenticates via npm Trusted Publisher (OIDC). No NPM_TOKEN. + # Requires @workflowbuilder/ui to have trusted publishing configured on + # npmjs.com pointing at this workflow file. + if: steps.check-version.outputs.already_published == 'false' + run: pnpm --filter @workflowbuilder/ui publish --no-git-checks --access public --provenance + + - name: Extract release notes from CHANGELOG + id: notes + run: | + VERSION=$(node -p "require('./packages/ui/package.json').version") + NOTES=$(awk -v v="$VERSION" '$0 ~ ("^## \\[?" v "\\]?([ -]|$)"){flag=1;next}/^## /{flag=0}flag' packages/ui/CHANGELOG.md) + { + echo "notes<> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} + body: ${{ steps.notes.outputs.notes }} + draft: false + prerelease: false diff --git a/CLAUDE.md b/CLAUDE.md index b7e76c160..6788454bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,21 +6,21 @@ Visual workflow editor SDK (React) with a reference backend and Temporal-based e Three onboarding paths (A installs from npm; B, C run the repo locally). README "Get started" covers all three. Path A ("Embed the SDK") installs `@workflowbuilder/sdk` from npm; the README has install + a minimal snippet, and the full guide lives in the [docs site](https://www.workflowbuilder.io/docs/get-started/quick-start/wb-as-react-component/). -| Command | Path | What it does | -| ---------------------------- | ---- | --------------------------------------------------------------------------- | -| `pnpm preflight` | B/C | Verify Node / pnpm / Docker / ports / `.env` files. Add `--json` for agents | -| `pnpm dev` / `pnpm dev:demo` | B | Demo (UI only, port 4200). No backend, no Docker | -| `pnpm infra:up` | C | Start Postgres + Temporal in Docker. Required before backend/worker | -| `pnpm -F backend db:migrate` | C | Apply Drizzle migrations out-of-band (backend also auto-migrates on boot) | -| `pnpm dev:ai-studio` | C | Full stack: infra + backend (3001) + worker + AI Studio frontend (4201) | -| `pnpm dev:backend` | C | Backend only (debug). Needs infra up | -| `pnpm dev:worker` | C | Execution worker only (debug). Needs infra up | -| `pnpm infra:down` | C | Stop the Docker stack | -| `pnpm dev:docs` | - | Docs site (Astro + Starlight) | -| `pnpm build:lib` | - | Build the SDK package (`packages/sdk`) | -| `pnpm build` | - | Build the demo app | -| `pnpm test` | - | Run tests in `packages/sdk` and `packages/execution-core` | -| `pnpm check` | - | Lint + typecheck + format + knip | +| Command | Path | What it does | +| ---------------------------- | ---- | --------------------------------------------------------------------------------------- | +| `pnpm preflight` | B/C | Verify Node / pnpm / Docker / ports / `.env` files. Add `--json` for agents | +| `pnpm dev` / `pnpm dev:demo` | B | Demo (UI only, port 4200). No backend, no Docker | +| `pnpm infra:up` | C | Start Postgres + Temporal in Docker. Required before backend/worker | +| `pnpm -F backend db:migrate` | C | Apply Drizzle migrations out-of-band (backend also auto-migrates on boot) | +| `pnpm dev:ai-studio` | C | Full stack: infra + backend (3001) + worker + AI Studio frontend (4201) | +| `pnpm dev:backend` | C | Backend only (debug). Needs infra up | +| `pnpm dev:worker` | C | Execution worker only (debug). Needs infra up | +| `pnpm infra:down` | C | Stop the Docker stack | +| `pnpm dev:docs` | - | Docs site (Astro + Starlight) | +| `pnpm build:lib` | - | Build the publishable chain: `ui-tokens` -> `ui` -> SDK (`build:ui` does the first two) | +| `pnpm build` | - | Build the demo app | +| `pnpm test` | - | Run tests in `packages/sdk` and `packages/execution-core` | +| `pnpm check` | - | Lint + typecheck + format + knip | Path B is UI-only and does not need Docker. Path C requires `pnpm infra:up` before backend/worker can start; the backend applies pending migrations automatically at boot. @@ -55,6 +55,8 @@ apps/ tools/ - @workflow-builder/tools workspace (decision-log collector, lint-staged config) packages/ sdk/ - @workflowbuilder/sdk public package (WorkflowBuilder compound component, plugin API, components) + ui/ - @workflowbuilder/ui published component library (Base UI), consumed by sdk/demo/ai-studio + tokens/ - @workflowbuilder/ui-tokens private design-token build (style-dictionary), feeds packages/ui execution-core/ - Pure topological graph runner + node executor registry types/ - Shared TypeScript types ``` @@ -65,20 +67,23 @@ Where to put a new script: root `tools/` for pure-Node bootstrap (runs before an Each workspace has its own context. Read the relevant file before extending a workspace. -| Workspace | Authoritative docs | -| ------------------------- | ----------------------------------- | -| `packages/sdk` | `packages/sdk/README.md` | -| `packages/execution-core` | `packages/execution-core/README.md` | -| `apps/demo` | `apps/demo/CLAUDE.md` | -| `apps/ai-studio` | `apps/ai-studio/README.md` | -| `apps/backend` | `apps/backend/README.md` | -| `apps/execution-worker` | `apps/execution-worker/README.md` | +| Workspace | Authoritative docs | +| ------------------------- | ------------------------------------------------------- | +| `packages/sdk` | `packages/sdk/README.md` | +| `packages/ui` | `packages/ui/README.md` (+ `packages/ui/css-layers.md`) | +| `packages/tokens` | `packages/tokens/README.md` | +| `packages/execution-core` | `packages/execution-core/README.md` | +| `apps/demo` | `apps/demo/CLAUDE.md` | +| `apps/ai-studio` | `apps/ai-studio/README.md` | +| `apps/backend` | `apps/backend/README.md` | +| `apps/execution-worker` | `apps/execution-worker/README.md` | ## Types & Aliases Shared types: `packages/types/` (imported as `@workflow-builder/types/*`). Icons: `apps/icons/` (imported as `@workflow-builder/icons`). SDK: `packages/sdk/` (imported as `@workflowbuilder/sdk`). +UI: `packages/ui/` (imported as `@workflowbuilder/ui`; styles via `@workflowbuilder/ui/styles.css`, `/index.css`, `/tokens.css`). ## Local Infrastructure @@ -93,14 +98,14 @@ Backend reads `DATABASE_URL` and `TEMPORAL_ADDRESS`; defaults work out of the bo ## Code Quality -| Tool | Command | Notes | -| ---------- | ----------------------------- | --------------------------------------------------------- | -| ESLint | `pnpm lint` / `pnpm lint:fix` | Per-workspace configs | -| Prettier | `pnpm format` | Sorts imports via `@trivago/prettier-plugin-sort-imports` | -| TypeScript | `pnpm typecheck` | Per-workspace `tsconfig.json` | -| Knip | Part of `pnpm check` | Detects unused exports/dependencies | -| Vitest | `pnpm test` | Runs in `packages/sdk` and `packages/execution-core` | -| Full check | `pnpm check` | Run before PR | +| Tool | Command | Notes | +| ---------- | ----------------------------- | --------------------------------------------------------------------------------------- | +| ESLint | `pnpm lint` / `pnpm lint:fix` | Per-workspace configs | +| Prettier | `pnpm format` | Sorts imports via `@trivago/prettier-plugin-sort-imports` | +| TypeScript | `pnpm typecheck` | Per-workspace `tsconfig.json` | +| Knip | Part of `pnpm check` | Detects unused exports/dependencies | +| Vitest | `pnpm test` | Runs in `packages/sdk`, `packages/execution-core`, `packages/ui`, and `packages/tokens` | +| Full check | `pnpm check` | Run before PR | ## Getting Started @@ -133,7 +138,7 @@ If you're new to this repo and want to build your own consumer app or POC, follo ### Releasing `@workflowbuilder/sdk` -The SDK is the only npm-published workspace; everything else under `apps/` and `packages/` is private (and listed under `ignore` in `.changeset/config.json`). +Two workspaces are npm-published: `@workflowbuilder/sdk` and `@workflowbuilder/ui` (the component library, built on Base UI). Everything else under `apps/` and `packages/` is private - including `@workflowbuilder/ui-tokens` - so Changesets skips it automatically; the internal `@workflow-builder/*` packages are additionally listed under `ignore` in `.changeset/config.json` (note `@workflowbuilder/ui-tokens` is not in that list - it relies on `private: true`). Both publish via scoped release tags (`@workflowbuilder/sdk@X.Y.Z`, `@workflowbuilder/ui@X.Y.Z`), each with its own workflow (`release-sdk.yml`, `release-ui.yml`) - see `packages/sdk/RELEASE.md`. **Commit format is enforced.** Every commit goes through `commitlint` via the `commit-msg` husky hook — Conventional Commits format only (`(): `, types from `feat / fix / perf / refactor / docs / test / chore / build / ci / style / revert`). Bad messages are rejected before they land in git history. @@ -158,7 +163,7 @@ The SDK is the only npm-published workspace; everything else under `apps/` and ` 4. GitHub Action triggered by the tag runs lint + typecheck + test + `pnpm publish --provenance` (authenticated via npm Trusted Publisher / OIDC, no `NPM_TOKEN` stored anywhere) + creates a GitHub Release. 5. Sync back: `git checkout main && git merge release && git push` so main picks up the bumped version + clean `.changeset/`. -Tag format follows the ng-diagram convention (single-package monorepo, `v*` regex). If we ever publish a second package we'll migrate to scoped (`@workflowbuilder/sdk@X.Y.Z`) — ~1h of work, see `packages/sdk/RELEASE.md` § "Why these decisions". +Tags are scoped per package (`@workflowbuilder/sdk@X.Y.Z`, `@workflowbuilder/ui@X.Y.Z`); each package has its own tag-triggered workflow (`release-sdk.yml`, `release-ui.yml`). The earlier single-package `v*` scheme was retired when `@workflowbuilder/ui` became publishable. See `packages/sdk/RELEASE.md` § "Why these decisions". Canonical procedure with edge cases and rollback: [`packages/sdk/RELEASE.md`](packages/sdk/RELEASE.md). diff --git a/knip.config.js b/knip.config.js index fafa88889..c65a0ede9 100644 --- a/knip.config.js +++ b/knip.config.js @@ -56,5 +56,16 @@ export default { project: ['**/*.{mjs,ts,astro}'], ignoreDependencies: ['@iconify-json/ph'], }, + 'packages/ui': { + entry: ['src/index.ts', 'vite.config.mts', 'scripts/check-built-css.ts'], + project: ['src/**/*.{ts,tsx}', '*.mts', 'scripts/**/*.ts'], + // Built tokens are copied by relative path (../tokens/dist) in vite.config, + // so the workspace dep is real even though it is never imported by name. + ignoreDependencies: ['@workflowbuilder/ui-tokens'], + }, + 'packages/tokens': { + entry: ['src/index.ts'], + project: ['src/**/*.ts', 'config.ts'], + }, }, }; diff --git a/package.json b/package.json index 5e0f48423..92a18d43d 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,15 @@ "db:migrate": "pnpm --filter backend db:migrate", "build": "pnpm --filter @workflow-builder/demo build", "build:ai-studio": "pnpm --filter @workflow-builder/ai-studio build", - "build:lib": "pnpm --filter @workflowbuilder/sdk build:lib", + "build:ui": "pnpm --filter @workflowbuilder/ui-tokens build && pnpm --filter @workflowbuilder/ui build", + "build:lib": "pnpm build:ui && pnpm --filter @workflowbuilder/sdk build:lib", "build:docs": "pnpm --filter @workflow-builder/docs build", "preview-build": "pnpm --filter @workflow-builder/demo preview-build", "lint": "pnpm -r lint", "lint:fix": "pnpm -r lint:fix", "format": "prettier --write --log-level silent \"**/*.+(css|ts|tsx|json|md|mdx|astro)\"", "typecheck": "pnpm -r typecheck", - "test": "pnpm --filter @workflowbuilder/sdk test && pnpm --filter @workflow-builder/execution-core test", + "test": "pnpm --filter @workflowbuilder/sdk test && pnpm --filter @workflow-builder/execution-core test && pnpm --filter @workflowbuilder/ui test && pnpm --filter @workflowbuilder/ui-tokens test", "check": "pnpm lint && pnpm typecheck && pnpm format", "pre-commit": "lint-staged", "pre-push": "pnpm format", diff --git a/packages/sdk/RELEASE.md b/packages/sdk/RELEASE.md index f10d1c546..01d497102 100644 --- a/packages/sdk/RELEASE.md +++ b/packages/sdk/RELEASE.md @@ -2,7 +2,16 @@ Maintainer-only procedure. Consumer docs live in [`README.md`](./README.md). High-level overview in monorepo root [`CLAUDE.md`](../../CLAUDE.md) → "Releasing `@workflowbuilder/sdk`". -The flow is **A+ (Changesets + commitlint + release branch + tag-triggered CI publish)** — mirrors [synergycodes/ng-diagram](https://github.com/synergycodes/ng-diagram) (single-package release flow with `v*` tags, defensive pre-publish checks, npm-view idempotency) plus Changesets for automated version/CHANGELOG management. +The flow is **A+ (Changesets + commitlint + release branch + tag-triggered CI publish)** — adapted from [synergycodes/ng-diagram](https://github.com/synergycodes/ng-diagram) (defensive pre-publish checks, npm-view idempotency) plus Changesets for automated version/CHANGELOG management. + +This repo publishes **two** packages: `@workflowbuilder/sdk` and `@workflowbuilder/ui`. Each has its own **scoped** release tag and its own workflow: + +| Package | Tag format | Workflow | +| ---------------------- | ---------------------------- | ----------------------------------- | +| `@workflowbuilder/sdk` | `@workflowbuilder/sdk@X.Y.Z` | `.github/workflows/release-sdk.yml` | +| `@workflowbuilder/ui` | `@workflowbuilder/ui@X.Y.Z` | `.github/workflows/release-ui.yml` | + +The steps below describe the SDK; the UI release is identical with the UI tag/workflow/package path substituted. (The old single-package `v*` tag scheme has been retired - scoped tags are required so the two packages don't collide.) ## Mental model @@ -15,8 +24,8 @@ main ─────●───●───●───●─────● release ───────────────●─────────── ... │ └ Each commit on `release` = one published version. - Tag `vX.Y.Z` lives on that commit. - Tag push → GitHub Action publishes to npm. + Tag `@workflowbuilder/@X.Y.Z` lives on that commit. + Tag push → GitHub Action publishes that package to npm. ``` `main` is "what we're building"; `release` is "what's currently on npm". The tag is the single source of truth for "this exact commit became version X.Y.Z". @@ -25,14 +34,14 @@ release ───────────────●─────── 1. **npm organization access.** Get added as a maintainer on the `workflowbuilder` npm organization (or create it the first time at ). The org name has no hyphen, matching the scope `@workflowbuilder/sdk`. -2. **Configure the npm Trusted Publisher.** Authentication is OIDC. No `NPM_TOKEN` is used or stored. On (or for a not-yet-published package: org settings → "Packages" → "Add trusted publisher"), add: +2. **Configure the npm Trusted Publisher (once per package).** Authentication is OIDC. No `NPM_TOKEN` is used or stored. Each package needs its own trusted publisher pointing at its own workflow file. On the package's `…/access` page (or for a not-yet-published package: org settings → "Packages" → "Add trusted publisher"), add: - Publisher: **GitHub Actions** - Organization or user: `synergycodes` - Repository: `workflowbuilder` - - Workflow filename: `release-sdk.yml` + - Workflow filename: `release-sdk.yml` for `@workflowbuilder/sdk`, `release-ui.yml` for `@workflowbuilder/ui` - Environment name: _(leave empty)_ - The workflow already has `permissions: id-token: write`, so once the trusted publisher is registered, `pnpm publish` on a `v*` tag push exchanges the GitHub OIDC token for a short-lived npm credential. Provenance attestation is enabled via the `--provenance` flag, so each published version links back to the exact workflow run and commit on . + The workflows already have `permissions: id-token: write`, so once the trusted publisher is registered, `pnpm publish` on a scoped tag push exchanges the GitHub OIDC token for a short-lived npm credential. Provenance attestation is enabled via the `--provenance` flag, so each published version links back to the exact workflow run and commit. 3. **Create the `release` branch** (first time only): @@ -165,7 +174,7 @@ In the PR diff you should see: - `packages/sdk/package.json`: version bump - `packages/sdk/CHANGELOG.md`: new Keep-a-Changelog section (dated `## [X.Y.Z]` heading, `### Added` / `### Changed` / `### Fixed` groupings, link reference at the bottom), reformatted from the raw Changesets output - `.changeset/*.md`: deletions -- `pnpm-lock.yaml`: small workspace dep update (only if internal `workspace:*` packages bumped — currently they don't because everything is in `ignore`) +- `pnpm-lock.yaml`: small workspace dep update if a tracked package was bumped. The non-publishable internal packages are in `ignore` in `.changeset/config.json`, but `@workflowbuilder/sdk` and `@workflowbuilder/ui` are tracked - bumping `@workflowbuilder/ui` updates consumers that depend on it via `workspace:*` Local smoke before approving: @@ -193,8 +202,9 @@ After the merge lands on `release`: ```bash git checkout release && git pull -git tag vX.Y.Z # matches the published version exactly -git push origin vX.Y.Z +git tag @workflowbuilder/sdk@X.Y.Z # scoped; matches packages/sdk/package.json exactly +git push origin @workflowbuilder/sdk@X.Y.Z +# For the UI package: git tag @workflowbuilder/ui@X.Y.Z && git push origin @workflowbuilder/ui@X.Y.Z ``` ### 5. CI publishes automatically @@ -240,8 +250,8 @@ A published version cannot be overwritten on npm. Options when something went wr - **Tag is on the wrong commit but version is not yet on npm** (CI failed mid-publish, or you killed the workflow before publish step ran): delete and re-tag: ```bash - git tag -d vX.Y.Z - git push origin :refs/tags/vX.Y.Z + git tag -d @workflowbuilder/sdk@X.Y.Z + git push origin :refs/tags/@workflowbuilder/sdk@X.Y.Z # … fix the underlying issue, then re-tag at the correct commit and push again ``` @@ -269,9 +279,7 @@ A published version cannot be overwritten on npm. Options when something went wr - **OIDC Trusted Publisher, never a long-lived `NPM_TOKEN`** — the workflow exchanges a per-run GitHub OIDC token for a short-lived npm credential. Nothing to rotate, nothing to leak from CI logs. The trust is bound to the exact `synergycodes/workflowbuilder` repository plus this workflow file path; a fork can't publish, a different workflow in the same repo can't publish. `--provenance` attaches the signed build attestation so consumers see "published from this commit, by this workflow run" on the npm page. -- **Tag format `vX.Y.Z`** — adopted from ng-diagram convention used elsewhere in the Synergy Codes org. Shorter, idiomatic for single-package monorepos, plays well with GitHub UI / `gh release` / external tooling that defaults to `v*` regex. - - **If we ever publish a second package** (e.g. `@workflowbuilder/types`), we'll migrate the tag format to scoped (`@workflowbuilder/sdk@X.Y.Z`) so the two packages can be released independently without tag collisions. Migration is ~1h of work: update the workflow trigger pattern, the version-verify step, and this file's "Tag the merge commit" section. Historical `v*` tags stay untouched in git history — the migration is forward-only, no rewriting. Until then, `v*` keeps the daily flow short. +- **Scoped tag format `@workflowbuilder/@X.Y.Z`** — the repo now publishes two packages (`@workflowbuilder/sdk` and `@workflowbuilder/ui`), so each release tag is scoped to its package. This lets the two be released independently without tag collisions, and lets each workflow trigger on its own tag pattern. The earlier single-package `v*` scheme (an ng-diagram convention for single-package monorepos) was retired when `@workflowbuilder/ui` became publishable. Historical `v*` tags stay untouched in git history — the change is forward-only. - **Dedicated `release` branch** — `main` is "what we're building", `release` is "what's currently on npm". Each commit on `release` corresponds to one published version. Why this over main-only: - "What's published" is visible as a branch in the UI (no `git tag --list` scanning). diff --git a/packages/tokens/README.md b/packages/tokens/README.md new file mode 100644 index 000000000..09496385d --- /dev/null +++ b/packages/tokens/README.md @@ -0,0 +1,47 @@ +# @workflowbuilder/ui-tokens + +Private design-token build for `@workflowbuilder/ui`. Turns Figma-exported tokens into the +`--ax-*` CSS variables the component library ships (light/dark themes + primitives). + +Not published to npm (`private: true`); consumed only as a workspace build step. + +## How it works + +1. `tokens.json` - raw token export from Figma (via Tokens Studio), checked into the repo. +2. `src/manifest.ts` - validates `config.ts` against the `tokens.json` exports and derives + every file path once; all later steps consume the manifest instead of re-deriving names. +3. `src/eject-tokens.ts` - splits `tokens.json` into one JSON file per theme/primitive set + under `dist/tokens/`. +4. `src/tokens-to-css.ts` - runs each set through [Style Dictionary](https://styledictionary.com/) + (with `@tokens-studio/sd-transforms`) to emit `dist/.css` files scoped to + `html[data-theme='light']` / `html[data-theme='dark']`. A broken token reference fails + the build (`brokenReferences: 'throw'`). +5. `src/generate-css-bundle.ts` - inlines the primitive and theme CSS into a single + self-contained `dist/tokens.css` (no `@import`, so the file survives being copied out + alone). + +`config.ts` declares which primitive and theme sets get processed, using set names exactly +as `tokens.json` exports them (e.g. `Tokens/Dark`); a name mismatch fails the build with +the available keys listed. + +`packages/ui`'s build copies `dist/tokens.css` (and the primitive CSS files) out of this +package's `dist/` and re-exports them as `@workflowbuilder/ui/tokens.css` - see +`packages/ui/vite.config.mts`. + +## Build + +```bash +pnpm --filter @workflowbuilder/ui-tokens build +``` + +Runs the pipeline above and writes everything to `dist/`. Also runs automatically as this +package's `prepare` script, and as the first step of `pnpm build:ui` / `pnpm build:lib` at +the repo root. + +## Test + +```bash +pnpm --filter @workflowbuilder/ui-tokens test +``` + +Vitest unit tests (currently `src/to-file-name.spec.ts` and `src/manifest.spec.ts`). diff --git a/packages/tokens/config.ts b/packages/tokens/config.ts new file mode 100644 index 000000000..6005e97ce --- /dev/null +++ b/packages/tokens/config.ts @@ -0,0 +1,17 @@ +import { Config } from './src/types'; + +// Set names must match tokens.json exports verbatim - buildManifest() validates +// this at build start and derives all file paths from them. +export const config: Config = { + primitives: ['Numerals/Mode 1', 'Primitives/Mode 1'], + themes: [ + { + set: 'Tokens/Dark', + selector: "html[data-theme='dark']", + }, + { + set: 'Tokens/Light', + selector: "html[data-theme='light']", + }, + ], +}; diff --git a/packages/tokens/eslint.config.mjs b/packages/tokens/eslint.config.mjs new file mode 100644 index 000000000..eee9610de --- /dev/null +++ b/packages/tokens/eslint.config.mjs @@ -0,0 +1 @@ +export { default } from '../../eslint.config.mjs'; diff --git a/packages/tokens/lint-staged.config.mjs b/packages/tokens/lint-staged.config.mjs new file mode 100644 index 000000000..63809e0a3 --- /dev/null +++ b/packages/tokens/lint-staged.config.mjs @@ -0,0 +1 @@ +export { default } from '../../lint-staged.config.mjs'; diff --git a/packages/tokens/package.json b/packages/tokens/package.json new file mode 100644 index 000000000..7d7fa2012 --- /dev/null +++ b/packages/tokens/package.json @@ -0,0 +1,23 @@ +{ + "name": "@workflowbuilder/ui-tokens", + "version": "0.0.0", + "private": true, + "type": "module", + "files": [ + "dist" + ], + "scripts": { + "build": "tsx src/index.ts", + "prepare": "tsx src/index.ts", + "typecheck": "tsc --noEmit", + "lint": "eslint", + "test": "vitest run" + }, + "devDependencies": { + "@tokens-studio/sd-transforms": "^1.2.12", + "remeda": "^2.21.2", + "style-dictionary": "^4.3.3", + "tsx": "^4.19.3", + "vitest": "^3.0.4" + } +} diff --git a/packages/tokens/src/constants.ts b/packages/tokens/src/constants.ts new file mode 100644 index 000000000..3f73bf00f --- /dev/null +++ b/packages/tokens/src/constants.ts @@ -0,0 +1,2 @@ +export const OUTPUT_DIR = './dist/'; +export const TOKEN_OUTPUT_DIR = `${OUTPUT_DIR}tokens/`; diff --git a/packages/tokens/src/eject-tokens.ts b/packages/tokens/src/eject-tokens.ts new file mode 100644 index 000000000..e75091cc3 --- /dev/null +++ b/packages/tokens/src/eject-tokens.ts @@ -0,0 +1,21 @@ +import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; + +import tokens from '../tokens.json'; +import { TOKEN_OUTPUT_DIR } from './constants'; +import { Manifest } from './types'; + +export function ejectTokens({ primitives, themes }: Manifest): void { + // No try/catch on purpose: this is a build step whose only product is correct + // token JSON. A write failure must abort the build (non-zero exit) instead of + // letting tokensToCss() run against missing/stale files and emit broken CSS. + if (!existsSync(TOKEN_OUTPUT_DIR)) { + mkdirSync(TOKEN_OUTPUT_DIR, { recursive: true }); + } + + for (const entry of [...primitives, ...themes]) { + const tokenSet = (tokens as Record)[entry.key]; + + console.log(entry.jsonPath); + writeFileSync(entry.jsonPath, JSON.stringify(tokenSet, null, 2), 'utf8'); + } +} diff --git a/packages/tokens/src/generate-css-bundle.ts b/packages/tokens/src/generate-css-bundle.ts new file mode 100644 index 000000000..0a3644048 --- /dev/null +++ b/packages/tokens/src/generate-css-bundle.ts @@ -0,0 +1,17 @@ +import { readFile, writeFile } from 'node:fs/promises'; + +import { OUTPUT_DIR } from './constants'; +import { Manifest } from './types'; + +/** + * Composes `dist/tokens.css` as a fully self-contained stylesheet: primitives + * and themes are inlined, never `@import`-ed. The file must survive being + * copied out alone (public/ dirs, CDNs, constructed stylesheets ignore + * `@import`) - a relative import would break silently there. + */ +export async function generateCSSBundle({ primitives, themes }: Manifest) { + const sources = [...primitives, ...themes]; + const chunks = await Promise.all(sources.map(({ cssPath }) => readFile(cssPath, 'utf8'))); + + return writeFile(`${OUTPUT_DIR}tokens.css`, chunks.join('\n')); +} diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts new file mode 100644 index 000000000..832b3ac58 --- /dev/null +++ b/packages/tokens/src/index.ts @@ -0,0 +1,18 @@ +import fs from 'node:fs'; + +import { tokensToCss } from './tokens-to-css'; + +import { OUTPUT_DIR } from './constants'; +import { ejectTokens } from './eject-tokens'; +import { generateCSSBundle } from './generate-css-bundle'; +import { buildManifest } from './manifest'; + +// Stale outputs must not survive a rebuild: a renamed set leaves its old file +// behind, and an incremental UI build would ship it as if it were current. +fs.rmSync(OUTPUT_DIR, { recursive: true, force: true }); + +const manifest = buildManifest(); + +ejectTokens(manifest); +await tokensToCss(manifest); +await generateCSSBundle(manifest); diff --git a/packages/tokens/src/manifest.spec.ts b/packages/tokens/src/manifest.spec.ts new file mode 100644 index 000000000..087610815 --- /dev/null +++ b/packages/tokens/src/manifest.spec.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import { buildManifest } from './manifest'; + +const exportedKeys = ['Primitives/Mode 1', 'Numerals/Mode 1', 'Tokens/Dark', '$themes', '$metadata']; + +describe('buildManifest', () => { + it('derives every path from the tokens.json key once', () => { + const manifest = buildManifest(exportedKeys, { + primitives: ['Primitives/Mode 1'], + themes: [{ set: 'Tokens/Dark', selector: "html[data-theme='dark']" }], + }); + + expect(manifest.primitives).toEqual([ + { + key: 'Primitives/Mode 1', + fileName: 'primitives-mode-1', + jsonPath: './dist/tokens/primitives-mode-1.json', + cssPath: './dist/primitives-mode-1.css', + }, + ]); + expect(manifest.themes[0]).toMatchObject({ + fileName: 'tokens-dark', + selector: "html[data-theme='dark']", + }); + }); + + it('rejects a config set that tokens.json does not export, listing what it does', () => { + expect(() => + buildManifest(exportedKeys, { + primitives: [], + themes: [{ set: 'Tokens/Drak', selector: 'html' }], + }), + ).toThrowError(/'Tokens\/Drak'.+tokens\.json exports: Primitives\/Mode 1/); + }); + + it('rejects a set listed more than once, also across groups', () => { + expect(() => + buildManifest(exportedKeys, { + primitives: ['Primitives/Mode 1'], + themes: [{ set: 'Primitives/Mode 1', selector: 'html' }], + }), + ).toThrowError(/'Primitives\/Mode 1'.+more than once/); + }); + + it('does not treat $-metadata keys as exported sets', () => { + expect(() => + buildManifest(exportedKeys, { + primitives: ['$themes'], + themes: [], + }), + ).toThrowError(/'\$themes'/); + }); + + it('rejects distinct keys that normalize to the same file name', () => { + expect(() => + buildManifest(['Tokens/Dark', 'Tokens Dark'], { + primitives: ['Tokens Dark'], + themes: [{ set: 'Tokens/Dark', selector: 'html' }], + }), + ).toThrowError(/'Tokens Dark' and 'Tokens\/Dark' both resolve to 'tokens-dark'/); + }); +}); diff --git a/packages/tokens/src/manifest.ts b/packages/tokens/src/manifest.ts new file mode 100644 index 000000000..af4fd10ca --- /dev/null +++ b/packages/tokens/src/manifest.ts @@ -0,0 +1,71 @@ +import { config as defaultConfig } from '../config'; +import tokens from '../tokens.json'; +import { OUTPUT_DIR, TOKEN_OUTPUT_DIR } from './constants'; +import { toFileName } from './to-file-name'; +import { Config, Manifest, TokenSetEntry } from './types'; + +/** + * The single place where token-set names become file paths. + * + * Validates the config against the tokens.json exports up front, so a renamed + * Figma set or a config typo fails the build immediately with the available + * keys listed - instead of surfacing later as a missing file or an empty + * theme. Every pipeline step consumes the returned entries; none re-derives + * a path on its own. + */ +export function buildManifest( + exportedKeys: string[] = Object.keys(tokens), + { primitives, themes }: Config = defaultConfig, +): Manifest { + const exportedSets = new Set(exportedKeys.filter((key) => !key.startsWith('$'))); + + const requested = [...primitives, ...themes.map((theme) => theme.set)]; + + const missing = requested.filter((key) => !exportedSets.has(key)); + if (missing.length > 0) { + throw new Error( + `config.ts references ${missing.map((key) => `'${key}'`).join(', ')} ` + + `but tokens.json exports: ${[...exportedSets].join(', ')}`, + ); + } + + // A set listed twice (or in both groups) would make two builds race for the + // same output file - the second silently overwrites the first. + const duplicates = requested.filter((key, index) => requested.indexOf(key) !== index); + if (duplicates.length > 0) { + throw new Error(`config.ts lists ${[...new Set(duplicates)].map((key) => `'${key}'`).join(', ')} more than once`); + } + + // Distinct keys can still normalize to the same file name ('Tokens Dark' + // and 'Tokens/Dark' both become tokens-dark) - the same silent-overwrite + // race, one step later. + const keysByFileName = new Map(); + for (const key of requested) { + const fileName = toFileName(key); + keysByFileName.set(fileName, [...(keysByFileName.get(fileName) ?? []), key]); + } + const collisions = [...keysByFileName.entries()].filter(([, keys]) => keys.length > 1); + if (collisions.length > 0) { + throw new Error( + collisions + .map(([fileName, keys]) => `${keys.map((key) => `'${key}'`).join(' and ')} both resolve to '${fileName}'`) + .join('; '), + ); + } + + return { + primitives: primitives.map(entryFor), + themes: themes.map((theme) => ({ ...entryFor(theme.set), selector: theme.selector })), + }; +} + +function entryFor(key: string): TokenSetEntry { + const fileName = toFileName(key); + + return { + key, + fileName, + jsonPath: `${TOKEN_OUTPUT_DIR}${fileName}.json`, + cssPath: `${OUTPUT_DIR}${fileName}.css`, + }; +} diff --git a/packages/tokens/src/to-file-name.spec.ts b/packages/tokens/src/to-file-name.spec.ts new file mode 100644 index 000000000..fa6cc0c6a --- /dev/null +++ b/packages/tokens/src/to-file-name.spec.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest'; + +import { toFileName } from './to-file-name'; + +describe('toFileName', () => { + it('replaces slashes with hyphens', () => { + expect(toFileName('primitives/mode-1')).toBe('primitives-mode-1'); + }); + + it('kebab-cases camelCase names', () => { + expect(toFileName('numeralsMode')).toBe('numerals-mode'); + }); + + it('kebab-cases a slash-separated camelCase path', () => { + expect(toFileName('semanticTokens/lightTheme')).toBe('semantic-tokens-light-theme'); + }); + + it('leaves an already-kebab name unchanged', () => { + expect(toFileName('tokens')).toBe('tokens'); + }); +}); diff --git a/packages/tokens/src/to-file-name.ts b/packages/tokens/src/to-file-name.ts new file mode 100644 index 000000000..0defc7485 --- /dev/null +++ b/packages/tokens/src/to-file-name.ts @@ -0,0 +1,8 @@ +import { toKebabCase } from 'remeda'; + +export function toFileName(name: string) { + const escapedPropertyName = name.replaceAll('/', '-'); + const fileName = toKebabCase(escapedPropertyName); + + return fileName; +} diff --git a/packages/tokens/src/tokens-to-css.ts b/packages/tokens/src/tokens-to-css.ts new file mode 100644 index 000000000..05284d4ac --- /dev/null +++ b/packages/tokens/src/tokens-to-css.ts @@ -0,0 +1,88 @@ +import { register } from '@tokens-studio/sd-transforms'; +import path from 'node:path'; +import StyleDictionary, { Config, TransformedToken } from 'style-dictionary'; + +import { OUTPUT_DIR } from './constants'; +import { Manifest, TokenSetEntry } from './types'; + +register(StyleDictionary); + +export async function tokensToCss(manifest: Manifest) { + await processPrimitiveTokens(manifest.primitives); + await processThemeTokens(manifest); +} + +async function processPrimitiveTokens(primitives: TokenSetEntry[]): Promise { + for (const primitive of primitives) { + const config = createSDConfig({ + fileName: primitive.fileName, + source: [primitive.jsonPath], + }); + + const styleDictionary = new StyleDictionary(config); + await styleDictionary.buildAllPlatforms(); + } +} + +async function processThemeTokens({ primitives, themes }: Manifest): Promise { + const primitiveSources = primitives.map((primitive) => primitive.jsonPath); + + for (const theme of themes) { + const config = createSDConfig({ + fileName: theme.fileName, + source: [...primitiveSources, theme.jsonPath], + selector: theme.selector, + // Compare the source file's basename, not a substring of the whole path: + // a set whose kebab name happens to appear in the directory prefix + // (e.g. a set named 'Tokens' vs ./dist/tokens/) must not match. + filter: (token) => + !primitives.some((primitive) => path.basename(token.filePath) === `${primitive.fileName}.json`), + }); + + const styleDictionary = new StyleDictionary(config); + await styleDictionary.buildAllPlatforms(); + } +} + +function createSDConfig({ fileName, selector, source, filter }: SDConfigParams) { + return { + source, + preprocessors: ['tokens-studio'], + platforms: { + css: { + transformGroup: 'tokens-studio', + transforms: ['name/kebab'], + buildPath: OUTPUT_DIR, + options: { + outputReferences: true, + selector, + }, + files: [ + { + destination: `${fileName}.css`, + filter, + format: 'css/variables', + }, + ], + }, + }, + log: logOptions, + } as Config; +} + +type SDConfigParams = { + fileName: string; + source: string[]; + selector?: string; + filter?: (token: TransformedToken) => boolean; +}; + +const logOptions = { + warnings: 'disabled', // 'warn' | 'error' | 'disabled' + verbosity: 'verbose', // 'default' | 'silent' | 'verbose' + errors: { + // A dangling reference has no legitimate case - fail the build instead of + // shipping CSS with literal `{token.path}` values the browser discards. + brokenReferences: 'throw', // 'throw' | 'console' + }, +} satisfies Config['log']; diff --git a/packages/tokens/src/types.ts b/packages/tokens/src/types.ts new file mode 100644 index 000000000..c0fac5910 --- /dev/null +++ b/packages/tokens/src/types.ts @@ -0,0 +1,27 @@ +export type Config = { + primitives: string[]; + themes: ThemeConfig[]; +}; + +type ThemeConfig = { + /** Exact token-set key as exported in tokens.json (e.g. 'Tokens/Dark'). */ + set: string; + selector: string; +}; + +/** One token set with every path the pipeline needs, derived once in buildManifest(). */ +export type TokenSetEntry = { + /** Exact tokens.json key. */ + key: string; + /** Kebab-cased basename shared by the ejected JSON and the built CSS. */ + fileName: string; + jsonPath: string; + cssPath: string; +}; + +type ThemeEntry = TokenSetEntry & { selector: string }; + +export type Manifest = { + primitives: TokenSetEntry[]; + themes: ThemeEntry[]; +}; diff --git a/packages/tokens/tokens.json b/packages/tokens/tokens.json new file mode 100644 index 000000000..728b803d8 --- /dev/null +++ b/packages/tokens/tokens.json @@ -0,0 +1,3408 @@ +{ + "Primitives/Mode 1": { + "ax": { + "colors": { + "gray-100": { + "value": "#ffffff", + "type": "color" + }, + "gray-200": { + "value": "#f5f5f7", + "type": "color" + }, + "gray-300": { + "value": "#edeff3", + "type": "color" + }, + "gray-400": { + "value": "#d5d8e0", + "type": "color" + }, + "gray-500": { + "value": "#6f7480", + "type": "color" + }, + "gray-600": { + "value": "#4d5059", + "type": "color" + }, + "gray-700": { + "value": "#27282b", + "type": "color" + }, + "gray-800": { + "value": "#151516", + "type": "color" + }, + "gray-900": { + "value": "#070708", + "type": "color" + }, + "blue-100": { + "value": "#eef2fd", + "type": "color" + }, + "blue-200": { + "value": "#e3e8f5", + "type": "color" + }, + "blue-300": { + "value": "#d0d8eb", + "type": "color" + }, + "blue-400": { + "value": "#336dff", + "type": "color" + }, + "blue-500": { + "value": "#2b5bd5", + "type": "color" + }, + "blue-600": { + "value": "#2249ab", + "type": "color" + }, + "green-100": { + "value": "#e9f7ee", + "type": "color" + }, + "green-200": { + "value": "#c2edd1", + "type": "color" + }, + "green-300": { + "value": "#29974e", + "type": "color" + }, + "green-400": { + "value": "#007c29", + "type": "color" + }, + "orange-100": { + "value": "#f7f2e9", + "type": "color" + }, + "orange-200": { + "value": "#eddfc2", + "type": "color" + }, + "orange-300": { + "value": "#ffaf10", + "type": "color" + }, + "orange-400": { + "value": "#e59800", + "type": "color" + }, + "red-100": { + "value": "#f7e9e9", + "type": "color" + }, + "red-200": { + "value": "#edc2c2", + "type": "color" + }, + "red-300": { + "value": "#deadad", + "type": "color" + }, + "red-400": { + "value": "#962929", + "type": "color" + }, + "red-500": { + "value": "#7d0000", + "type": "color" + }, + "red-600": { + "value": "#670000", + "type": "color" + }, + "gray-650": { + "value": "#383a40", + "type": "color" + }, + "gray-450": { + "value": "#bdc1cc", + "type": "color" + }, + "green-400-10": { + "value": "#007c291a", + "type": "color" + }, + "orange-400-10": { + "value": "#e598001a", + "type": "color" + }, + "red-400-10": { + "value": "#9629291a", + "type": "color" + }, + "red-400-20": { + "value": "#96292933", + "type": "color" + }, + "red-400-50": { + "value": "#96292980", + "type": "color" + }, + "gray-100-50": { + "value": "#ffffff80", + "type": "color" + }, + "gray-100-30": { + "value": "#ffffff4d", + "type": "color" + }, + "gray-900-50": { + "value": "#07070880", + "type": "color" + }, + "gray-900-30": { + "value": "#0707084d", + "type": "color" + }, + "gray-100-75": { + "value": "#ffffffbf", + "type": "color" + }, + "red-400-30": { + "value": "#9629294d", + "type": "color" + }, + "red-100-10": { + "value": "#f7e9e91a", + "type": "color" + }, + "red-100-20": { + "value": "#f7e9e933", + "type": "color" + }, + "red-100-30": { + "value": "#f7e9e94d", + "type": "color" + }, + "red-100-50": { + "value": "#f7e9e980", + "type": "color" + }, + "blue-400-10": { + "value": "#336dff1a", + "type": "color" + }, + "blue-400-20": { + "value": "#336dff33", + "type": "color" + }, + "blue-400-30": { + "value": "#336dff4d", + "type": "color" + }, + "blue-400-50": { + "value": "#336dff80", + "type": "color" + }, + "green-400-20": { + "value": "#007c2933", + "type": "color" + }, + "green-400-30": { + "value": "#007c294d", + "type": "color" + }, + "green-400-50": { + "value": "#007c2980", + "type": "color" + }, + "orange-400-20": { + "value": "#e5980033", + "type": "color" + }, + "orange-400-30": { + "value": "#e598004d", + "type": "color" + }, + "orange-400-50": { + "value": "#e5980080", + "type": "color" + }, + "gray-100-10": { + "value": "#ffffff1a", + "type": "color" + }, + "gray-100-20": { + "value": "#ffffff33", + "type": "color" + }, + "gray-900-10": { + "value": "#0707081a", + "type": "color" + }, + "gray-900-20": { + "value": "#07070833", + "type": "color" + }, + "gray-900-5": { + "value": "#0707080d", + "type": "color" + }, + "gray-900-75": { + "value": "#07070880", + "type": "color" + }, + "gray-100-5": { + "value": "#ffffff0d", + "type": "color" + }, + "blue-350": { + "value": "#729aff", + "type": "color" + }, + "acc1-50": { + "value": "#f0f8ff", + "type": "color" + }, + "acc1-100": { + "value": "#e0f0fe", + "type": "color" + }, + "acc1-200": { + "value": "#bbe2fc", + "type": "color" + }, + "acc1-300": { + "value": "#5fbefa", + "type": "color" + }, + "acc1-400": { + "value": "#3ab0f6", + "type": "color" + }, + "acc1-500": { + "value": "#1096e7", + "type": "color" + }, + "acc1-600": { + "value": "#0477c5", + "type": "color" + }, + "acc1-700": { + "value": "#045fa0", + "type": "color" + }, + "acc1-800": { + "value": "#085184", + "type": "color" + }, + "acc1-900": { + "value": "#0d446d", + "type": "color" + }, + "acc1-950": { + "value": "#092b48", + "type": "color" + }, + "acc1-500-10": { + "value": "#1096e71a", + "type": "color" + }, + "acc1-500-20": { + "value": "#1096e733", + "type": "color" + }, + "acc1-500-30": { + "value": "#1096e74d", + "type": "color" + }, + "acc1-500-50": { + "value": "#1096e780", + "type": "color" + }, + "acc1-500-40": { + "value": "#1096e766", + "type": "color" + }, + "acc1-950-10": { + "value": "#092b481a", + "type": "color" + }, + "acc1-950-20": { + "value": "#092b4833", + "type": "color" + }, + "acc1-950-30": { + "value": "#092b484d", + "type": "color" + }, + "acc1-950-50": { + "value": "#092b4880", + "type": "color" + }, + "acc1-950-40": { + "value": "#092b4866", + "type": "color" + }, + "acc2-50": { + "value": "#fef2f2", + "type": "color" + }, + "acc2-100": { + "value": "#fee3e2", + "type": "color" + }, + "acc2-200": { + "value": "#fdcdcb", + "type": "color" + }, + "acc2-300": { + "value": "#fa928e", + "type": "color" + }, + "acc2-400": { + "value": "#f77772", + "type": "color" + }, + "acc2-500": { + "value": "#ed4c46", + "type": "color" + }, + "acc2-500-10": { + "value": "#ed4c461a", + "type": "color" + }, + "acc2-500-20": { + "value": "#ed4c4633", + "type": "color" + }, + "acc2-500-30": { + "value": "#ed4c464d", + "type": "color" + }, + "acc2-500-40": { + "value": "#ed4c4666", + "type": "color" + }, + "acc2-500-50": { + "value": "#ed4c4680", + "type": "color" + }, + "acc2-600": { + "value": "#da2f28", + "type": "color" + }, + "acc2-700": { + "value": "#b7241e", + "type": "color" + }, + "acc2-800": { + "value": "#98211c", + "type": "color" + }, + "acc2-900": { + "value": "#7e221e", + "type": "color" + }, + "acc2-950": { + "value": "#440d0b", + "type": "color" + }, + "acc2-950-10": { + "value": "#440d0b1a", + "type": "color" + }, + "acc2-950-20": { + "value": "#440d0b33", + "type": "color" + }, + "acc2-950-30": { + "value": "#440d0b4d", + "type": "color" + }, + "acc2-950-40": { + "value": "#440d0b66", + "type": "color" + }, + "acc2-950-50": { + "value": "#440d0b80", + "type": "color" + }, + "acc3-50": { + "value": "#effef7", + "type": "color" + }, + "acc3-100": { + "value": "#cafde6", + "type": "color" + }, + "acc3-200": { + "value": "#7efac4", + "type": "color" + }, + "acc3-300": { + "value": "#57f1b1", + "type": "color" + }, + "acc3-400": { + "value": "#24dd90", + "type": "color" + }, + "acc3-500": { + "value": "#0bc175", + "type": "color" + }, + "acc3-600": { + "value": "#069b62", + "type": "color" + }, + "acc3-700": { + "value": "#0a7b52", + "type": "color" + }, + "acc3-800": { + "value": "#0d6244", + "type": "color" + }, + "acc3-900": { + "value": "#10513b", + "type": "color" + }, + "acc3-950": { + "value": "#023121", + "type": "color" + }, + "acc4-50": { + "value": "#fbf5ff", + "type": "color" + }, + "acc4-100": { + "value": "#f6e9fe", + "type": "color" + }, + "acc4-200": { + "value": "#eed6fe", + "type": "color" + }, + "acc4-300": { + "value": "#e0b6fc", + "type": "color" + }, + "acc4-400": { + "value": "#d395f9", + "type": "color" + }, + "acc4-500": { + "value": "#ba5af2", + "type": "color" + }, + "acc4-600": { + "value": "#a839e4", + "type": "color" + }, + "acc4-700": { + "value": "#9127c9", + "type": "color" + }, + "acc4-800": { + "value": "#7a25a4", + "type": "color" + }, + "acc4-900": { + "value": "#641f84", + "type": "color" + }, + "acc4-950": { + "value": "#450a61", + "type": "color" + }, + "acc5-50": { + "value": "#fff4ed", + "type": "color" + }, + "acc5-100": { + "value": "#fee7d6", + "type": "color" + }, + "acc5-200": { + "value": "#fccdac", + "type": "color" + }, + "acc5-300": { + "value": "#faae73", + "type": "color" + }, + "acc5-400": { + "value": "#f79240", + "type": "color" + }, + "acc5-500": { + "value": "#f4841b", + "type": "color" + }, + "acc5-600": { + "value": "#e57711", + "type": "color" + }, + "acc5-700": { + "value": "#be5310", + "type": "color" + }, + "acc5-800": { + "value": "#973c15", + "type": "color" + }, + "acc5-900": { + "value": "#7a2f14", + "type": "color" + }, + "acc5-950": { + "value": "#421608", + "type": "color" + }, + "acc3-500-10": { + "value": "#0bc1751a", + "type": "color" + }, + "acc3-500-20": { + "value": "#0bc17533", + "type": "color" + }, + "acc3-500-30": { + "value": "#0bc1754d", + "type": "color" + }, + "acc3-500-50": { + "value": "#0bc17580", + "type": "color" + }, + "acc4-500-10": { + "value": "#ba5af21a", + "type": "color" + }, + "acc4-500-20": { + "value": "#ba5af233", + "type": "color" + }, + "acc4-500-30": { + "value": "#ba5af24d", + "type": "color" + }, + "acc4-500-50": { + "value": "#ba5af280", + "type": "color" + }, + "acc5-500-10": { + "value": "#f4841b1a", + "type": "color" + }, + "acc5-500-20": { + "value": "#f4841b33", + "type": "color" + }, + "acc5-500-30": { + "value": "#f4841b4d", + "type": "color" + }, + "acc5-500-50": { + "value": "#f4841b80", + "type": "color" + }, + "acc3-500-40": { + "value": "#0bc17566", + "type": "color" + }, + "acc4-500-40": { + "value": "#ba5af266", + "type": "color" + }, + "acc5-500-40": { + "value": "#f4841b66", + "type": "color" + }, + "acc6-50": { + "value": "#f1f2ff", + "type": "color" + }, + "acc6-100": { + "value": "#dddffe", + "type": "color" + }, + "acc6-200": { + "value": "#bbbefc", + "type": "color" + }, + "acc6-300": { + "value": "#999efb", + "type": "color" + }, + "acc6-400": { + "value": "#777df9", + "type": "color" + }, + "acc6-500": { + "value": "#555df8", + "type": "color" + }, + "acc6-600": { + "value": "#444ac6", + "type": "color" + }, + "acc6-700": { + "value": "#333895", + "type": "color" + }, + "acc6-800": { + "value": "#222563", + "type": "color" + }, + "acc6-900": { + "value": "#111332", + "type": "color" + }, + "acc6-950": { + "value": "#080a24", + "type": "color" + }, + "acc7-50": { + "value": "#e3e3e3", + "type": "color" + }, + "acc7- 100": { + "value": "#cfd0d6", + "type": "color" + }, + "acc7- 200": { + "value": "#a0a1ad", + "type": "color" + }, + "acc7- 300": { + "value": "#707184", + "type": "color" + }, + "acc7- 400": { + "value": "#41425b", + "type": "color" + }, + "acc7- 500": { + "value": "#111332", + "type": "color" + }, + "acc7- 600": { + "value": "#0e0f28", + "type": "color" + }, + "acc7- 700": { + "value": "#0a0b1e", + "type": "color" + }, + "acc7- 800": { + "value": "#070814", + "type": "color" + }, + "acc7- 900": { + "value": "#03040a", + "type": "color" + }, + "acc7- 950": { + "value": "#010208", + "type": "color" + }, + "acc7-100": { + "value": "#cfd0d6", + "type": "color" + }, + "acc7-200": { + "value": "#a0a1ad", + "type": "color" + }, + "acc7-300": { + "value": "#707184", + "type": "color" + }, + "acc7-400": { + "value": "#41425b", + "type": "color" + }, + "acc7-500": { + "value": "#111332", + "type": "color" + }, + "acc7-600": { + "value": "#0e0f28", + "type": "color" + }, + "acc7-700": { + "value": "#0a0b1e", + "type": "color" + }, + "acc7-800": { + "value": "#070814", + "type": "color" + }, + "acc7-900": { + "value": "#03040a", + "type": "color" + }, + "acc7-950": { + "value": "#010208", + "type": "color" + } + } + } + }, + "Tokens/Light": { + "ax": { + "button-primary-bg-default": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-hover": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "button-primary-bg-active": { + "value": "{ax.colors.acc1-700}", + "type": "color" + }, + "button-primary-bg-focus": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-loading": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-gray-bg-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "button-gray-bg-hover": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "button-gray-bg-active": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "button-gray-bg-focus": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "button-gray-bg-loading": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "button-gray-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-red-bg-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-hover": { + "value": "{ax.colors.red-500}", + "type": "color" + }, + "button-red-bg-active": { + "value": "{ax.colors.red-600}", + "type": "color" + }, + "button-red-bg-focus": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-loading": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-green-bg-default": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-hover": { + "value": "{ax.colors.green-400}", + "type": "color" + }, + "button-green-bg-active": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-focus": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-loading": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-orange-bg-default": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-hover": { + "value": "{ax.colors.orange-400}", + "type": "color" + }, + "button-orange-bg-active": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-focus": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-loading": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "ui-bg-primary-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "ui-stroke-primary-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "node-bg-primary-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-stroke-primary-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "node-stroke-primary-hover": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "txt-primary-default": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "ui-bg-secondary-default": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "txt-secondary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "txt-tertiary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "ui-bg-tertiary-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "node-bg-secondary-default": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "input-stroke-primary-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "input-stroke-primary-focus": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "input-stroke-primary-error": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "input-stroke-primary-success": { + "value": "{ax.colors.green-400}", + "type": "color" + }, + "txt-quaternary-default": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "txt-error-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "txt-success-default": { + "value": "{ax.colors.green-400}", + "type": "color" + }, + "input-bg-primary-success": { + "value": "{ax.colors.green-400-10}", + "type": "color" + }, + "input-bg-primary-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "dropdown-bg-primary-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "dropdown-bg-secondary-active": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "dropdown-bg-destructive-hover": { + "value": "{ax.colors.red-400-20}", + "type": "color" + }, + "dropdown-bg-secondary-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "ui-bg-tertiary-selected": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "nav-button-bg-primary-hover": { + "value": "{ax.colors.gray-900-5}", + "type": "color" + }, + "nav-button-icon-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "nav-button-icon-primary-active": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "nav-button-icon-primary-disabled": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "ui-stroke-primary-focus": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "txt-primary-white": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "ui-separator-primary-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "ui-stroke-secondary-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "txt-primary-disabled": { + "value": "{ax.colors.gray-100-75}", + "type": "color" + }, + "pt-bg-primary-default": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "pt-stroke-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "txt-secondary-inverse": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "button-ghost-destructive-bg-default": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-hover": { + "value": "{ax.colors.red-400-20}", + "type": "color" + }, + "button-ghost-destructive-bg-active": { + "value": "{ax.colors.red-400-30}", + "type": "color" + }, + "button-ghost-destructive-bg-focus": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-loading": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-disabled": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-stroke-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "txt-destuctive-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "txt-destructive-disabled": { + "value": "{ax.colors.red-400-50}", + "type": "color" + }, + "dropdown-bg-destructive-default": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "snackbar-bg-default": { + "value": "{ax.colors.gray-900-5}", + "type": "color" + }, + "snackbar-bg-information": { + "value": "{ax.colors.acc1-500-10}", + "type": "color" + }, + "snackbar-bg-warning": { + "value": "{ax.colors.orange-400-10}", + "type": "color" + }, + "snackbar-bg-success": { + "value": "{ax.colors.green-400-10}", + "type": "color" + }, + "snackbar-bg-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "txt-info-default": { + "value": "{ax.colors.acc1-700}", + "type": "color" + }, + "txt-warning-default": { + "value": "{ax.colors.orange-400}", + "type": "color" + }, + "node-icon-primary-default": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "node-icon-primary-disabled": { + "value": "{ax.colors.gray-900-30}", + "type": "color" + }, + "node-txt-disabled": { + "value": "{ax.colors.gray-900-30}", + "type": "color" + }, + "node-bg-primary-disabled": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "tooltip-bg-default": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "tooltip-bg-blue": { + "value": "{ax.colors.acc1-800}", + "type": "color" + }, + "nav-button-icon-primary-hover": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "node-bg-primary-hover": { + "value": "{ax.colors.acc1-50}", + "type": "color" + }, + "ui-canvas-dots-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "txt-tooltip-bw": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "txt-tooltip-blue": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-port-fill-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-port-fill-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "node-port-stroke-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "node-port-stroke-active": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-notify-bg-default": { + "value": "{ax.colors.orange-400}", + "type": "color" + }, + "node-notify-ico-default": { + "value": "{ax.colors.orange-100}", + "type": "color" + }, + "scrollbar-bg-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "chips-neutral-txt": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "chips-neutral-bg": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "chips-neutral-icon": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "chips-neutral-x": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "shadow": { + "value": "{ax.colors.gray-900-20}", + "type": "color" + }, + "focus-ring-node-active": { + "value": "{ax.colors.acc1-500-40}", + "type": "color" + }, + "focus-ring-element": { + "value": "{ax.txt-primary-default}", + "type": "color" + }, + "chips-acc1-bg": { + "value": "{ax.colors.acc1-500-10}", + "type": "color" + }, + "chips-acc1-txt": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "chips-acc1-icon": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "chips-acc1-x": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "chips-acc1-stroke": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "chips-acc2-bg": { + "value": "{ax.colors.acc2-500-10}", + "type": "color" + }, + "chips-acc2-stroke": { + "value": "{ax.colors.acc2-600}", + "type": "color" + }, + "chips-acc2-txt": { + "value": "{ax.colors.acc2-600}", + "type": "color" + }, + "chips-acc2-icon": { + "value": "{ax.colors.acc2-600}", + "type": "color" + }, + "chips-acc2-x": { + "value": "{ax.colors.acc2-600}", + "type": "color" + }, + "chips-acc3-bg": { + "value": "{ax.colors.acc3-500-10}", + "type": "color" + }, + "chips-acc3-stroke": { + "value": "{ax.colors.acc3-700}", + "type": "color" + }, + "chips-acc3-txt": { + "value": "{ax.colors.acc3-700}", + "type": "color" + }, + "chips-acc3-icon": { + "value": "{ax.colors.acc3-700}", + "type": "color" + }, + "chips-acc3-x": { + "value": "{ax.colors.acc3-700}", + "type": "color" + }, + "chips-acc4-bg": { + "value": "{ax.colors.acc4-500-10}", + "type": "color" + }, + "chips-acc4-stroke": { + "value": "{ax.colors.acc4-600}", + "type": "color" + }, + "chips-acc4-txt": { + "value": "{ax.colors.acc4-600}", + "type": "color" + }, + "chips-acc4-icon": { + "value": "{ax.colors.acc4-600}", + "type": "color" + }, + "chips-acc4-x": { + "value": "{ax.colors.acc4-600}", + "type": "color" + }, + "chips-acc5-bg": { + "value": "{ax.colors.acc5-500-10}", + "type": "color" + }, + "chips-acc5-stroke": { + "value": "{ax.colors.acc5-700}", + "type": "color" + }, + "chips-acc5-txt": { + "value": "{ax.colors.acc5-700}", + "type": "color" + }, + "chips-acc5-icon": { + "value": "{ax.colors.acc5-700}", + "type": "color" + }, + "chips-acc5-x": { + "value": "{ax.colors.acc5-700}", + "type": "color" + }, + "focus-ring-node-warning": { + "value": "{ax.colors.acc5-500-40}", + "type": "color" + }, + "focus-ring-node-error": { + "value": "{ax.colors.acc2-500-40}", + "type": "color" + }, + "txt-primary-inverse": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-bg-tertiary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "node-bg-tertiary-hover": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "nav-button-icon-primary-pressed": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "ui-stroke-primary-highlight": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "tab-stroke-line": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "tab-stroke-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "tab-stroke-hover": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "tab-stroke-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "dropzone-bg-default": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "dropzone-bg-hover": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "dropzone-bg-dragging": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "dropzone-bg-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "dropzone-stroke-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "dropzone-stroke-hover": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "dropzone-stroke-dragging": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "dropzone-stroke-error": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "avatar-stroke-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "avatar-fill-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "link-primary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "link-primary-hover": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "link-primary-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "link-primary-disabled": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "label-stroke-primary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "label-stroke-primary-hover": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "label-stroke-primary-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "label-stroke-primary-disabled": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "edge-primary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "edge-primary-hover": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "edge-primary-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "edge-primary-disabled": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "widget-swatches-acc1": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "widget-swatches-acc2": { + "value": "{ax.colors.acc2-500}", + "type": "color" + }, + "widget-swatches-acc3": { + "value": "{ax.colors.acc3-500}", + "type": "color" + }, + "widget-swatches-acc4": { + "value": "{ax.colors.acc4-500}", + "type": "color" + }, + "widget-swatches-acc5": { + "value": "{ax.colors.acc5-500}", + "type": "color" + }, + "widget-swatches-acc6": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "datepicker-bg-primary": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "datepicker-bg-secondary": { + "value": "{ax.colors.gray-300}", + "type": "color" + } + } + }, + "Tokens/Dark": { + "ax": { + "button-primary-bg-default": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-hover": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "button-primary-bg-active": { + "value": "{ax.colors.acc1-700}", + "type": "color" + }, + "button-primary-bg-focus": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-loading": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "button-primary-bg-disabled": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "button-gray-bg-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "button-gray-bg-hover": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "button-gray-bg-active": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "button-gray-bg-focus": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "button-gray-bg-loading": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "button-gray-bg-disabled": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "button-red-bg-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-hover": { + "value": "{ax.colors.red-500}", + "type": "color" + }, + "button-red-bg-active": { + "value": "{ax.colors.red-600}", + "type": "color" + }, + "button-red-bg-focus": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-loading": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "button-red-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-green-bg-default": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-hover": { + "value": "{ax.colors.green-400}", + "type": "color" + }, + "button-green-bg-active": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-focus": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-loading": { + "value": "{ax.colors.green-300}", + "type": "color" + }, + "button-green-bg-disabled": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "button-orange-bg-default": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-hover": { + "value": "{ax.colors.orange-400}", + "type": "color" + }, + "button-orange-bg-active": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-focus": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "button-orange-bg-loading": { + "value": "{ax.colors.orange-300}", + "type": "color" + }, + "ui-bg-primary-default": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "ui-stroke-primary-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "node-bg-primary-default": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "node-stroke-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "node-stroke-primary-hover": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "txt-primary-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "ui-bg-secondary-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "txt-secondary-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "txt-tertiary-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "ui-bg-tertiary-default": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "node-bg-secondary-default": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "input-stroke-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "input-stroke-primary-focus": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "input-stroke-primary-error": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "input-stroke-primary-success": { + "value": "{ax.colors.green-400}", + "type": "color" + }, + "txt-quaternary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "txt-error-default": { + "value": "{ax.colors.red-100}", + "type": "color" + }, + "txt-success-default": { + "value": "{ax.colors.green-200}", + "type": "color" + }, + "input-bg-primary-success": { + "value": "{ax.colors.green-400-10}", + "type": "color" + }, + "input-bg-primary-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "dropdown-bg-primary-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "dropdown-bg-secondary-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "dropdown-bg-destructive-hover": { + "value": "{ax.colors.red-400-50}", + "type": "color" + }, + "dropdown-bg-secondary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "ui-bg-tertiary-selected": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "nav-button-bg-primary-hover": { + "value": "{ax.colors.gray-100-5}", + "type": "color" + }, + "nav-button-icon-primary-default": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "nav-button-icon-primary-active": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "nav-button-icon-primary-disabled": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "ui-stroke-primary-focus": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "txt-primary-white": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "ui-separator-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "ui-stroke-secondary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "txt-primary-disabled": { + "value": "{ax.colors.gray-100-30}", + "type": "color" + }, + "pt-bg-primary-default": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "pt-stroke-primary-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "txt-secondary-inverse": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "button-ghost-destructive-bg-default": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-hover": { + "value": "{ax.colors.red-400-20}", + "type": "color" + }, + "button-ghost-destructive-bg-active": { + "value": "{ax.colors.red-400-30}", + "type": "color" + }, + "button-ghost-destructive-bg-focus": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-loading": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-bg-disabled": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "button-ghost-destructive-stroke-default": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "txt-destuctive-default": { + "value": "{ax.colors.red-100}", + "type": "color" + }, + "txt-destructive-disabled": { + "value": "{ax.colors.red-100-30}", + "type": "color" + }, + "dropdown-bg-destructive-default": { + "value": "{ax.colors.red-400-30}", + "type": "color" + }, + "snackbar-bg-default": { + "value": "{ax.colors.gray-100-10}", + "type": "color" + }, + "snackbar-bg-information": { + "value": "{ax.colors.acc1-500-10}", + "type": "color" + }, + "snackbar-bg-warning": { + "value": "{ax.colors.orange-400-10}", + "type": "color" + }, + "snackbar-bg-success": { + "value": "{ax.colors.green-400-10}", + "type": "color" + }, + "snackbar-bg-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "txt-info-default": { + "value": "{ax.colors.acc1-200}", + "type": "color" + }, + "txt-warning-default": { + "value": "{ax.colors.orange-100}", + "type": "color" + }, + "node-icon-primary-default": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "node-icon-primary-disabled": { + "value": "{ax.colors.gray-100-30}", + "type": "color" + }, + "node-txt-disabled": { + "value": "{ax.colors.gray-100-30}", + "type": "color" + }, + "node-bg-primary-disabled": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "tooltip-bg-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "tooltip-bg-blue": { + "value": "{ax.colors.acc1-800}", + "type": "color" + }, + "nav-button-icon-primary-hover": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-bg-primary-hover": { + "value": "{ax.colors.acc1-950-10}", + "type": "color" + }, + "ui-canvas-dots-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "txt-tooltip-bw": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "txt-tooltip-blue": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-port-fill-default": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-port-fill-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "node-port-stroke-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "node-port-stroke-active": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "node-notify-bg-default": { + "value": "{ax.colors.orange-400}", + "type": "color" + }, + "node-notify-ico-default": { + "value": "{ax.colors.orange-100}", + "type": "color" + }, + "scrollbar-bg-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "chips-neutral-txt": { + "value": "{ax.colors.gray-100}", + "type": "color" + }, + "chips-neutral-bg": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "chips-neutral-icon": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "chips-neutral-x": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "shadow": { + "value": "{ax.colors.gray-900-50}", + "type": "color" + }, + "focus-ring-node-active": { + "value": "{ax.colors.acc1-500-40}", + "type": "color" + }, + "focus-ring-element": { + "value": "{ax.txt-primary-default}", + "type": "color" + }, + "chips-acc1-bg": { + "value": "{ax.colors.acc1-500-10}", + "type": "color" + }, + "chips-acc1-txt": { + "value": "{ax.colors.acc1-300}", + "type": "color" + }, + "chips-acc1-icon": { + "value": "{ax.colors.acc1-300}", + "type": "color" + }, + "chips-acc1-x": { + "value": "{ax.colors.acc1-300}", + "type": "color" + }, + "chips-acc1-stroke": { + "value": "{ax.colors.acc1-300}", + "type": "color" + }, + "chips-acc2-bg": { + "value": "{ax.colors.acc2-500-10}", + "type": "color" + }, + "chips-acc2-stroke": { + "value": "{ax.colors.acc2-300}", + "type": "color" + }, + "chips-acc2-txt": { + "value": "{ax.colors.acc2-300}", + "type": "color" + }, + "chips-acc2-icon": { + "value": "{ax.colors.acc2-300}", + "type": "color" + }, + "chips-acc2-x": { + "value": "{ax.colors.acc2-300}", + "type": "color" + }, + "chips-acc3-bg": { + "value": "{ax.colors.acc3-500-10}", + "type": "color" + }, + "chips-acc3-stroke": { + "value": "{ax.colors.acc3-300}", + "type": "color" + }, + "chips-acc3-txt": { + "value": "{ax.colors.acc3-300}", + "type": "color" + }, + "chips-acc3-icon": { + "value": "{ax.colors.acc3-300}", + "type": "color" + }, + "chips-acc3-x": { + "value": "{ax.colors.acc3-300}", + "type": "color" + }, + "chips-acc4-bg": { + "value": "{ax.colors.acc4-500-10}", + "type": "color" + }, + "chips-acc4-stroke": { + "value": "{ax.colors.acc4-300}", + "type": "color" + }, + "chips-acc4-txt": { + "value": "{ax.colors.acc4-300}", + "type": "color" + }, + "chips-acc4-icon": { + "value": "{ax.colors.acc4-300}", + "type": "color" + }, + "chips-acc4-x": { + "value": "{ax.colors.acc4-300}", + "type": "color" + }, + "chips-acc5-bg": { + "value": "{ax.colors.acc5-500-10}", + "type": "color" + }, + "chips-acc5-stroke": { + "value": "{ax.colors.acc5-200}", + "type": "color" + }, + "chips-acc5-txt": { + "value": "{ax.colors.acc5-200}", + "type": "color" + }, + "chips-acc5-icon": { + "value": "{ax.colors.acc5-200}", + "type": "color" + }, + "chips-acc5-x": { + "value": "{ax.colors.acc5-200}", + "type": "color" + }, + "focus-ring-node-warning": { + "value": "{ax.colors.acc5-500-40}", + "type": "color" + }, + "focus-ring-node-error": { + "value": "{ax.colors.acc2-500-40}", + "type": "color" + }, + "txt-primary-inverse": { + "value": "{ax.colors.gray-800}", + "type": "color" + }, + "node-bg-tertiary-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "node-bg-tertiary-hover": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "nav-button-icon-primary-pressed": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "ui-stroke-primary-highlight": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "tab-stroke-line": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "tab-stroke-default": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "tab-stroke-hover": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "tab-stroke-active": { + "value": "{ax.colors.acc1-500}", + "type": "color" + }, + "dropzone-bg-default": { + "value": "{ax.colors.gray-700}", + "type": "color" + }, + "dropzone-bg-hover": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "dropzone-bg-dragging": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "dropzone-bg-error": { + "value": "{ax.colors.red-400-10}", + "type": "color" + }, + "dropzone-stroke-default": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "dropzone-stroke-hover": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "dropzone-stroke-dragging": { + "value": "{ax.colors.gray-200}", + "type": "color" + }, + "dropzone-stroke-error": { + "value": "{ax.colors.red-400}", + "type": "color" + }, + "avatar-stroke-default": { + "value": "{ax.colors.gray-300}", + "type": "color" + }, + "avatar-fill-default": { + "value": "{ax.colors.gray-450}", + "type": "color" + }, + "link-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "link-primary-hover": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "link-primary-active": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "link-primary-disabled": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "label-stroke-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "label-stroke-primary-hover": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "label-stroke-primary-active": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "label-stroke-primary-disabled": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "edge-primary-default": { + "value": "{ax.colors.gray-600}", + "type": "color" + }, + "edge-primary-hover": { + "value": "{ax.colors.gray-500}", + "type": "color" + }, + "edge-primary-active": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "edge-primary-disabled": { + "value": "{ax.colors.gray-650}", + "type": "color" + }, + "widget-swatches-acc1": { + "value": "{ax.colors.acc1-400}", + "type": "color" + }, + "widget-swatches-acc2": { + "value": "{ax.colors.acc2-400}", + "type": "color" + }, + "widget-swatches-acc3": { + "value": "{ax.colors.acc3-400}", + "type": "color" + }, + "widget-swatches-acc4": { + "value": "{ax.colors.acc4-400}", + "type": "color" + }, + "widget-swatches-acc5": { + "value": "{ax.colors.acc5-400}", + "type": "color" + }, + "widget-swatches-acc6": { + "value": "{ax.colors.gray-400}", + "type": "color" + }, + "datepicker-bg-primary": { + "value": "{ax.colors.acc1-600}", + "type": "color" + }, + "datepicker-bg-secondary": { + "value": "{ax.colors.gray-800}", + "type": "color" + } + } + }, + "Numerals/Mode 1": { + "ax": { + "token-spacing": { + "spacing-32": { + "value": "{ax.primitive-4rule.32}", + "type": "dimension" + }, + "spacing-24": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "spacing-20": { + "value": "{ax.primitive-4rule.20}", + "type": "dimension" + }, + "spacing-16": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "spacing-12": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "spacing-8": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "spacing-4": { + "value": "{ax.primitive-4rule.4}", + "type": "dimension" + }, + "spacing-10": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "button-xl-h-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "button-xl-h-pad-2": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-xl-gap": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xl-v-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "button-xl-v-pad-2": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-l-h-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "button-l-h-pad-2": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-l-v-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "button-l-v-pad-2": { + "value": "{ax.primitive-odd.11}", + "type": "dimension" + }, + "button-l-gap": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-m-h-pad-1": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-m-h-pad-2": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-m-v-pad-1": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "button-m-v-pad-2": { + "value": "{ax.primitive-odd.9}", + "type": "dimension" + }, + "button-m-gap": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-s-h-pad-1": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "button-s-h-pad-2": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "button-s-v-pad-1": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "button-s-v-pad-2": { + "value": "{ax.primitive-odd.7}", + "type": "dimension" + }, + "button-s-gap": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xs-h-pad-1": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xs-h-pad-2": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xs-v-pad-1": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xs-v-pad-2": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xs-gap": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "button-xxs-h-pad": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxs-v-pad": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxxs-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxxs-v-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxxxs-h-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "button-xxxxs-v-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "input-l-h-pad": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "input-l-v-pad": { + "value": "{ax.primitive-odd.11}", + "type": "dimension" + }, + "input-l-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "input-m-h-pad": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "input-m-v-pad": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "input-m-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "input-s-h-pad": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "input-s-v-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "input-s-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-item-l-h-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-item-l-v-pad": { + "value": "{ax.primitive-odd.11}", + "type": "dimension" + }, + "dropdown-item-l-gap": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "dropdown-item-m-h-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-item-m-v-pad": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "dropdown-item-m-gap": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "dropdown-item-s-h-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-item-s-v-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-item-s-gap": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "snackbar-h-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "snackbar-v-pad-1": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "snackbar-v-pad-2": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "snackbar-gap-1": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "snackbar-gap-2": { + "value": "{ax.primitive-4rule.32}", + "type": "dimension" + }, + "node-head-icon": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "node-head-h-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "node-head-v-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "node-head-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "modal-m-header-pad": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "modal-m-header-gap": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "modal-m-content-h-pad": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "modal-m-content-gap": { + "value": "{ax.primitive-4rule.20}", + "type": "dimension" + }, + "modal-l-header-pad": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "modal-l-header-gap": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "modal-l-content-h-pad": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "modal-l-content-gap": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "modal-m-content-v-pad": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "modal-l-content-v-pad": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "product-tour-pad": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "product-tour-gap": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "chips-l-h-pad": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "chips-l-v-pad": { + "value": "{ax.primitive-odd.5}", + "type": "dimension" + }, + "chips-l-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-m-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-m-v-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "chips-m-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-s-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-s-v-pad": { + "value": "{ax.primitive-even.2}", + "type": "dimension" + }, + "chips-s-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-xl-h-pad": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "chips-xl-v-pad": { + "value": "{ax.primitive-odd.5}", + "type": "dimension" + }, + "chips-xl-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "tooltip-h-pad": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "tooltip-v-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "tooltip-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "top-navbar-h-pad": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "top-navbar-v-pad": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "top-navbar-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-wrap-s-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-s-v-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-s-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-m-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-m-v-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-m-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-l-h-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-l-v-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "dropdown-wrap-l-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "checkbox-m-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "checkbox-s-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "checkbox-xs-pad": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + }, + "radiobutton-m-pad": { + "value": "{ax.primitive-odd.5}", + "type": "dimension" + }, + "radiobutton-s-pad": { + "value": "{ax.primitive-odd.5}", + "type": "dimension" + }, + "radiobutton-xs-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxs-h-pad-1": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxs-v-pad-1": { + "value": "{ax.primitive-odd.7}", + "type": "dimension" + }, + "button-xxxs-h-pad-1": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "toolbar-hor-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "toolbar-vert-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "toolbar-hor-gap": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "toolbar-vert-gap": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxxs-v-pad-1": { + "value": "{ax.primitive-odd.5}", + "type": "dimension" + }, + "button-xxs-gap": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxxs-gap": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxxs-h-pad-2": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxxs-v-pad-2": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxs-h-pad-2": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-xxs-v-pad-2": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "seg-picker-xl-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xl-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-l-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-l-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-m-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-m-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-s-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-s-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xs-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xs-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xxs-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xxs-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xxxs-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-xxxs-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "label-gap": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "label-l-v-pad": { + "value": "{ax.primitive-even.18}", + "type": "dimension" + }, + "label-l-h-pad": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "label-m-v-pad": { + "value": "{ax.primitive-even.18}", + "type": "dimension" + }, + "label-m-h-pad": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "label-s-v-pad": { + "value": "{ax.primitive-even.16}", + "type": "dimension" + }, + "label-s-h-pad": { + "value": "{ax.primitive-odd.9}", + "type": "dimension" + }, + "label-s-v-pad-1": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "label-s-h-pad-1": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "label-m-v-pad-1": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "label-m-h-pad-1": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "label-xs-v-pad-1": { + "value": "{ax.primitive-odd.9}", + "type": "dimension" + }, + "label-xs-h-pad-1": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "label-m-h-pad-2": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "label-m-v-pad-2": { + "value": "{ax.primitive-odd.9}", + "type": "dimension" + }, + "label-s-h-pad-2": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "label-s-v-pad-2": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "label-xs-h-pad-2": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "label-xs-v-pad-2": { + "value": "{ax.primitive-odd.7}", + "type": "dimension" + }, + "seg-picker-pad": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-gap": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "datepicker-header-pad": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "datepicker-weekdays-pad-h": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "datepicker-weekdays-pad-v": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "datepicker-table-pad": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "datepicker-table-gap": { + "value": "{ax.primitive-even.2}", + "type": "dimension" + } + }, + "token-radius": { + "element-4": { + "value": "{ax.primitive-4rule.4}", + "type": "dimension" + }, + "element-8": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "element-12": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "element-16": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "element-20": { + "value": "{ax.primitive-4rule.20}", + "type": "dimension" + }, + "element-24": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "button-xl": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "button-l": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "button-m": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "button-s": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-round": { + "value": "{ax.primitive-even.100}", + "type": "dimension" + }, + "button-xxs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxxs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "button-xxxxs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "segment_picker_shell": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "segment_picker_element": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "input-l": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "input-m": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "input-s": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "dropdown-item-l": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "dropdown-item-m": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "dropdown-item-s": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "snackbar": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "node-head-icon": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "node-head": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "modal-l": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "modal-m": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "product-tour": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "product-tour-img": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "chips-l": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-m": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-s": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "chips-xl": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "tooltip": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "shell": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "dropdown-wrap-l": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-wrap-m": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "dropdown-wrap-s": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "checkbox-m": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "checkbox-s": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "checkbox-xs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "radiobutton": { + "value": "{ax.primitive-even.100}", + "type": "dimension" + }, + "toolbar-hor-s": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "toolbar-hor-m": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "toolbar-hor-l": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "toolbar-hor-xl": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "toolbar-vert-s": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "toolbar-vert-m": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "toolbar-vert-l": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "toolbar-vert-xl": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "seg-picker-xl": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "seg-picker-l": { + "value": "{ax.primitive-even.12}", + "type": "dimension" + }, + "seg-picker-m": { + "value": "{ax.primitive-even.10}", + "type": "dimension" + }, + "segt-picker-s": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "seg-picker-xs": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "seg-picker-xxs": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "seg-picker-xxxs": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "label-sharp": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "label-rounded": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "label-oval": { + "value": "{ax.primitive-even.100}", + "type": "dimension" + }, + "label-m": { + "value": "{ax.primitive-even.6}", + "type": "dimension" + }, + "label-s": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "label-xs": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "seg-picker-s": { + "value": "{ax.primitive-even.8}", + "type": "dimension" + }, + "datepicker-component": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "datepicker-day-rounded": { + "value": "{ax.primitive-4rule.4}", + "type": "dimension" + }, + "datepicker-day-sharp": { + "value": "{ax.primitive-4rule.0}", + "type": "dimension" + } + }, + "primitive-4rule": { + "0": { + "value": "0rem", + "type": "dimension" + }, + "4": { + "value": "0.25rem", + "type": "dimension" + }, + "8": { + "value": "0.5rem", + "type": "dimension" + }, + "12": { + "value": "0.75rem", + "type": "dimension" + }, + "16": { + "value": "1rem", + "type": "dimension" + }, + "20": { + "value": "1.25rem", + "type": "dimension" + }, + "24": { + "value": "1.5rem", + "type": "dimension" + }, + "28": { + "value": "1.75rem", + "type": "dimension" + }, + "32": { + "value": "2rem", + "type": "dimension" + }, + "36": { + "value": "2.25rem", + "type": "dimension" + }, + "40": { + "value": "2.5rem", + "type": "dimension" + }, + "44": { + "value": "2.75rem", + "type": "dimension" + }, + "48": { + "value": "3rem", + "type": "dimension" + }, + "52": { + "value": "3.25rem", + "type": "dimension" + }, + "56": { + "value": "3.5rem", + "type": "dimension" + }, + "60": { + "value": "3.75rem", + "type": "dimension" + }, + "64": { + "value": "4rem", + "type": "dimension" + } + }, + "primitive-even": { + "0": { + "value": "0rem", + "type": "dimension" + }, + "2": { + "value": "0.125rem", + "type": "dimension" + }, + "4": { + "value": "0.25rem", + "type": "dimension" + }, + "6": { + "value": "0.375rem", + "type": "dimension" + }, + "8": { + "value": "0.5rem", + "type": "dimension" + }, + "10": { + "value": "0.625rem", + "type": "dimension" + }, + "12": { + "value": "0.75rem", + "type": "dimension" + }, + "14": { + "value": "0.875rem", + "type": "dimension" + }, + "16": { + "value": "1rem", + "type": "dimension" + }, + "18": { + "value": "1.125rem", + "type": "dimension" + }, + "20": { + "value": "1.25rem", + "type": "dimension" + }, + "100": { + "value": "6.25rem", + "type": "dimension" + } + }, + "primitive-odd": { + "1": { + "value": "0.062rem", + "type": "dimension" + }, + "3": { + "value": "0.188rem", + "type": "dimension" + }, + "5": { + "value": "0.312rem", + "type": "dimension" + }, + "7": { + "value": "0.438rem", + "type": "dimension" + }, + "9": { + "value": "0.562rem", + "type": "dimension" + }, + "11": { + "value": "0.688rem", + "type": "dimension" + }, + "13": { + "value": "0.812rem", + "type": "dimension" + }, + "15": { + "value": "0.938rem", + "type": "dimension" + } + }, + "token-shadow": { + "shadow-xs-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "shadow-xs-y": { + "value": "{ax.primitive-4rule.4}", + "type": "dimension" + }, + "shadow-xs-blur": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "shadow-xs-spread": { + "value": "{ax.primitive-even-negative.-2}", + "type": "dimension" + }, + "shadow-s-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "shadow-s-y": { + "value": "{ax.primitive-4rule.8}", + "type": "dimension" + }, + "shadow-s-blur": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "shadow-s-spread": { + "value": "{ax.primitive-even-negative.-4}", + "type": "dimension" + }, + "shadow-m-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "shadow-m-y": { + "value": "{ax.primitive-4rule.12}", + "type": "dimension" + }, + "shadow-m-blur": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "shadow-m-spread": { + "value": "{ax.primitive-even-negative.-6}", + "type": "dimension" + }, + "shadow-l-blur": { + "value": "{ax.primitive-4rule.32}", + "type": "dimension" + }, + "shadow-l-spread": { + "value": "{ax.primitive-even-negative.-8}", + "type": "dimension" + }, + "shadow-l-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "shadow-l-y": { + "value": "{ax.primitive-4rule.16}", + "type": "dimension" + }, + "shadow-xl-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "shadow-xl-y": { + "value": "{ax.primitive-4rule.24}", + "type": "dimension" + }, + "shadow-xl-blur": { + "value": "{ax.primitive-4rule.48}", + "type": "dimension" + }, + "shadow-xl-spread": { + "value": "{ax.primitive-even-negative.-12}", + "type": "dimension" + }, + "focus-node-active-blur": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-node-active-spread": { + "value": "{ax.primitive-even.4}", + "type": "dimension" + }, + "focus-node-active-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-node-active-y": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-element-x": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-element-y": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-element-blur": { + "value": "{ax.primitive-even.0}", + "type": "dimension" + }, + "focus-element-spread": { + "value": "{ax.primitive-even.2}", + "type": "dimension" + } + }, + "primitive-even-negative": { + "-2": { + "value": "-0.125rem", + "type": "dimension" + }, + "-4": { + "value": "-0.25rem", + "type": "dimension" + }, + "-6": { + "value": "-0.375rem", + "type": "dimension" + }, + "-8": { + "value": "-0.5rem", + "type": "dimension" + }, + "-10": { + "value": "-0.625rem", + "type": "dimension" + }, + "-12": { + "value": "-0.75rem", + "type": "dimension" + }, + "-14": { + "value": "-0.875rem", + "type": "dimension" + }, + "-16": { + "value": "-1rem", + "type": "dimension" + } + }, + "token-stroke": { + "edge-regular": { + "value": "{ax.primitive-even.2}", + "type": "dimension" + }, + "edge-bold": { + "value": "{ax.primitive-odd.3}", + "type": "dimension" + } + } + } + }, + "$themes": [], + "$metadata": { + "tokenSetOrder": ["Primitives/Mode 1", "Tokens/Light", "Tokens/Dark", "Numerals/Mode 1"] + } +} diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json new file mode 100644 index 000000000..e9202c2d4 --- /dev/null +++ b/packages/tokens/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + // Node-only build scripts; lift lib to es2021 for String.replaceAll etc. + "lib": ["es2021"], + "types": ["node"] + } +} diff --git a/packages/tokens/vitest.config.mts b/packages/tokens/vitest.config.mts new file mode 100644 index 000000000..4ac6027d5 --- /dev/null +++ b/packages/tokens/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + }, +}); diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md new file mode 100644 index 000000000..d366cdef9 --- /dev/null +++ b/packages/ui/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog + +All notable changes to `@workflowbuilder/ui` are documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +> **Moved from `@synergycodes/overflow-ui`.** This library was previously +> published as +> [`@synergycodes/overflow-ui`](https://www.npmjs.com/package/@synergycodes/overflow-ui). +> It moved into the Workflow Builder monorepo, was rebuilt on +> [Base UI](https://base-ui.com/), and is released as `2.0.0` under the +> `@workflowbuilder/ui` name. The library's prior history lives in the old +> package's +> [changelog](https://github.com/synergycodes/overflow-ui/blob/main/packages/ui/CHANGELOG.md). + +## [2.0.0] + +First release of `@workflowbuilder/ui`: an accessible, themeable React +component library built on [Base UI](https://base-ui.com/), plus diagram +building blocks (node and edge parts). It is the styled layer behind the +Workflow Builder SDK. + +### Highlights + +- **Components**: Button, Input, TextArea, Select, Menu, Modal, Tooltip, Switch, + IconSwitch, Checkbox, RadioButton, SegmentPicker, DatePicker, Accordion, + Avatar, Snackbar, Status, Separator, plus the NodePanel / NodeIcon / + NodeDescription / EdgeLabel diagram primitives. +- **Theming** via `--ax-*` design tokens, isolated in cascade layers + (`@layer ui.base, ui.component`) so app styles win without `!important` and + components retheme cleanly. +- **Multi-entry build** with per-component subpath exports + (`@workflowbuilder/ui/`). Importing from the package root injects + all required styles; subpath imports inject only that component's CSS. Every + built stylesheet carries the `@layer` order statement, so import order never + affects the cascade; add `@workflowbuilder/ui/styles.css` once if you also + want the global reset and typography. +- **Dependencies**: `react` / `react-dom` are the only peer dependencies. + `@base-ui/react` (pinned to the validated `1.4.x` line) and + `react-textarea-autosize` are regular dependencies; `date-fns`, + `react-day-picker`, `clsx`, and the Phosphor icons are bundled into the + package output. + +### Migrating from `@synergycodes/overflow-ui` + +The library was rebuilt on Base UI (the previous MUI / Mantine / Emotion / +Floating UI stack is gone), so several public APIs changed: + +- **Menu**: `onOpenChange` is `(open: boolean, event?: Event)`; the MUI + `slotProps` / passthrough surface is removed. +- **Select**: `onChange` is `(event, value)`. +- **Switch**: `onChange` is `(checked: boolean, event: Event)`; the redundant + `styles` prop is removed - use `className`. +- **SegmentPicker**: `onChange` is + `(event: React.MouseEvent, value: string)`. +- **DatePicker**: rebuilt on `react-day-picker` + `date-fns`. The prop surface + is curated (`value`, `defaultValue`, `type`, `valueFormat`, `placeholder`, + `error`, `size`, `disabled`, `minDate`, `maxDate`, `onChange`, `id`, + `className`, `aria-label`, `aria-labelledby`). `valueFormat` uses `date-fns` + tokens; the legacy dayjs `DD/MM/YYYY` default is accepted and converted. +- **Modal**: `className` and forwarded HTML attributes apply to the root element. +- **shape** prop (Button / SegmentPicker): the type is `'default' | 'circle'` + (pass `'default'`, not `''`). + +[2.0.0]: https://www.npmjs.com/package/@workflowbuilder/ui/v/2.0.0 diff --git a/packages/ui/LICENSE b/packages/ui/LICENSE new file mode 100644 index 000000000..df87dddbb --- /dev/null +++ b/packages/ui/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2025-2026 Synergia Pro Sp. z o.o. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/ui/README.md b/packages/ui/README.md new file mode 100644 index 000000000..dcc7c27a6 --- /dev/null +++ b/packages/ui/README.md @@ -0,0 +1,164 @@ +# @workflowbuilder/ui + +Workflow Builder's component library: accessible, themeable React UI primitives - button, input, select, modal, menu, date picker, switch, tooltip, and more - built on [Base UI](https://base-ui.com), plus diagram building blocks (node and edge parts) for visual editors. + +Developed and maintained by **[Synergy Codes](https://www.synergycodes.com/)**. + +## Quick Start: 3-Minute Guide + +### 📦 Installation + +Use one of the commands below to add **`@workflowbuilder/ui`** to your project: + +```bash +npm install @workflowbuilder/ui +``` + +```bash +pnpm add @workflowbuilder/ui +``` + +```bash +yarn add @workflowbuilder/ui +``` + +> **Peers & dependencies.** The only peer dependencies are `react` and +> `react-dom` (bring your own). `@base-ui/react` is a regular dependency pinned +> to the `1.4.x` line (later versions regressed dialog/menu/tooltip transitions), +> so it installs automatically — no need to add it yourself. The heavier +> component dependencies (date-fns, react-day-picker, clsx, Phosphor icons) are +> bundled into the package; `react-textarea-autosize` and `@base-ui/react` are +> the only ones resolved from your `node_modules`. + +### 🎨 Import styles + +Import components from the package root (the recommended default). Their styles, +including the global layer order, reset, and typography, are injected +automatically, so you only need to add the design tokens: + +```css +@import '@workflowbuilder/ui/tokens.css'; +``` + +```tsx +import '@workflowbuilder/ui/tokens.css'; +``` + +> **Subpath imports.** You can also import a single component directly, e.g. +> `import { DatePicker } from '@workflowbuilder/ui/date-picker'`. That +> injects only the component's own CSS. Every stylesheet carries the cascade +> layer order, so import order doesn't matter; add +> `import '@workflowbuilder/ui/styles.css'` once if you also want the global +> reset and typography. + +### 🎛️ Apply the Theme + +To make the styles use proper variables, include data-theme (light or dark) attribute in : + +```html + +``` + +### 🧱 Use components + +```tsx +import { Input } from '@workflowbuilder/ui'; + +// … + +; +``` + +## Overview + +Forget cobbling together UI kits with diagram libraries. `@workflowbuilder/ui` provides a unified set of designed, ready-to-use components: buttons, inputs, accordions, node templates, and more — all built to work seamlessly together. + +## Features + +- Unified Component System: Seamlessly integrated UI and diagram components +- Ready-to-use Components: Comprehensive set of pre-built components +- Token-based Customization: Easy theming through CSS variables +- Developer-friendly: Focus on developer experience and productivity +- React Flow Compatible: Perfect for React Flow users with pre-built node templates that match React Flow's styling + +## Customization + +Each `@workflowbuilder/ui` component uses CSS variables that are derived from primitive values. + +You can override them: + +```css +:root { + --ax-ui-bg-primary-default: #40ba12; +} +``` + +or a derived value used by the selected component: + +```css +:root { + --ax-public-date-picker-dropdown-background: #40ba12; +} +``` + +### `@workflowbuilder/ui` css layers + +`@workflowbuilder/ui` uses [CSS layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) to separate its styles from yours. By default, CSS styles outside of any layer take precedence over what `@workflowbuilder/ui` defines, so your styles will always win the specificity war. You can customize `@workflowbuilder/ui` components with simple `input {}`. + +```css +@layer ui.component { + .separator { + /* … */ + } +} +``` + +Default `@workflowbuilder/ui` order: + +```css +@layer ui.base, ui.component; +``` + +# 🛠️ Development + +Run these from the monorepo root. + +Install dependencies: + +```bash +pnpm i +``` + +Build the library (runs the built-CSS guard afterwards): + +```bash +pnpm --filter @workflowbuilder/ui build +``` + +Rebuild on change while developing: + +```bash +pnpm --filter @workflowbuilder/ui dev +``` + +Run the unit tests: + +```bash +pnpm --filter @workflowbuilder/ui test +``` + +To see components rendered live, start the documentation site with `pnpm dev:docs` and open its UI Library gallery. + +### 📣 Important Note on Underlying Technology + +> **`@workflowbuilder/ui` is built on top of [Base UI](https://base-ui.com), a headless component library that focuses on accessibility and logic, while leaving the styling up to us.** +> +> Thanks to Base UI, `@workflowbuilder/ui` provides components that are **accessible by default** and **fully customizable** through our design tokens. +> +> The library was previously published as `@synergycodes/overflow-ui` on the now-deprecated [MUI Base](https://v6.mui.com/base-ui/getting-started/) stack. `2.0.0` is the first release rebuilt on Base UI under the `@workflowbuilder/ui` name; see [CHANGELOG.md](./CHANGELOG.md). + +## Showcase + +**[Workflow Builder](https://www.workflowbuilder.io/)** is a frontend-focused starter app for building workflows, offering core features, best practices, and easy backend integration for faster client delivery. + +https://www.workflowbuilder.io/ diff --git a/packages/ui/combine-css-bundle.mts b/packages/ui/combine-css-bundle.mts new file mode 100644 index 000000000..351944def --- /dev/null +++ b/packages/ui/combine-css-bundle.mts @@ -0,0 +1,96 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import type { Plugin } from 'vite'; + +/** + * Post-build CSS steps for the multi-entry library bundle. See css-layers.md. + * + * Emits `index.css` (all component styles, prefixed with the @layer order) + * and `styles.css` (the global layer order, reset and typography), then + * stamps the @layer order statement into every per-component stylesheet in + * `dist/assets/`. Duplicate statements are no-ops, so whichever stylesheet + * loads first establishes the correct order. Do not rely on import order + * instead: in the built barrel the injected per-chunk CSS evaluates before + * the entry CSS, so "the declaration loads first" is true in src/ but false + * in dist/ (the inverted-cascade bug class). + */ +export function combineCssBundle(rootDirectory: string): Plugin { + const distributionDirectory = path.resolve(rootDirectory, 'dist'); + const stylesDirectory = path.resolve(rootDirectory, 'src/styles'); + + return { + name: 'wb-ui:combine-css-bundle', + apply: 'build', + closeBundle() { + writeCombinedStylesheet(distributionDirectory, stylesDirectory); + writeGlobalStylesheet(distributionDirectory, stylesDirectory); + prependLayerOrderToAssets(distributionDirectory, stylesDirectory); + }, + }; +} + +function readLayerOrder(stylesDirectory: string): string { + return fs.readFileSync(path.resolve(stylesDirectory, 'layers.css'), 'utf8').trim(); +} + +function assetsDirectoryOf(distributionDirectory: string): string { + const assetsDirectory = path.resolve(distributionDirectory, 'assets'); + + if (!fs.existsSync(assetsDirectory)) { + throw new Error( + `wb-ui:combine-css-bundle: ${assetsDirectory} is missing - ` + + 'the Vite build emitted no per-component CSS, so the published entrypoints would be broken', + ); + } + + return assetsDirectory; +} + +function cssFilesIn(assetsDirectory: string): string[] { + const files = fs + .readdirSync(assetsDirectory) + .filter((file) => file.endsWith('.css')) + .sort(); + + if (files.length === 0) { + throw new Error( + `wb-ui:combine-css-bundle: ${assetsDirectory} contains no CSS - ` + + 'index.css would carry only the @layer order and no component rules', + ); + } + + return files; +} + +function writeCombinedStylesheet(distributionDirectory: string, stylesDirectory: string) { + const assetsDirectory = assetsDirectoryOf(distributionDirectory); + + // Within a layer, file order only breaks ties between equal-specificity rules. + const styles = cssFilesIn(assetsDirectory) + .map((file) => fs.readFileSync(path.resolve(assetsDirectory, file), 'utf8')) + .join('\n'); + + const combined = `${readLayerOrder(stylesDirectory)}\n${styles}`; + fs.writeFileSync(path.resolve(distributionDirectory, 'index.css'), combined); +} + +function writeGlobalStylesheet(distributionDirectory: string, stylesDirectory: string) { + const globals = ['layers.css', 'globals.css', 'typography.css'] + .map((file) => fs.readFileSync(path.resolve(stylesDirectory, file), 'utf8')) + .join('\n'); + + fs.writeFileSync(path.resolve(distributionDirectory, 'styles.css'), globals); +} + +function prependLayerOrderToAssets(distributionDirectory: string, stylesDirectory: string) { + const assetsDirectory = assetsDirectoryOf(distributionDirectory); + const layerOrder = readLayerOrder(stylesDirectory); + + for (const file of cssFilesIn(assetsDirectory)) { + const filePath = path.resolve(assetsDirectory, file); + const content = fs.readFileSync(filePath, 'utf8'); + + if (content.startsWith(layerOrder)) continue; + fs.writeFileSync(filePath, `${layerOrder}\n${content}`); + } +} diff --git a/packages/ui/css-layers.md b/packages/ui/css-layers.md new file mode 100644 index 000000000..4e478ed4b --- /dev/null +++ b/packages/ui/css-layers.md @@ -0,0 +1,63 @@ +# CSS layers + +`@workflowbuilder/ui` emits all of its styles into two ordered cascade layers: + +```css +@layer ui.base, ui.component; +``` + +`ui.base` holds resets, primitives, and vendored third-party stylesheets +(react-day-picker); `ui.component` holds component styles. Declaring the order +before any rule from either layer guarantees that `ui.component` always wins +over `ui.base`, and that unlayered consumer styles win over both. + +## Establishing the order + +The order is fixed by the **first** `@layer` declaration the browser sees, so +the declaration must load before any component rule. To make that hold on +every path, the build (`combine-css-bundle.mts`) stamps the full order +statement at the top of **every** emitted stylesheet - the combined +`index.css`, `styles.css`, and each per-component file in `dist/assets/`. +Duplicate statements are no-ops (the first occurrence fixes the order, later +ones confirm it), so any import order and any bundler is safe: + +- importing from the package root just works - whichever injected chunk + stylesheet loads first carries the statement; +- per-component subpath imports just work for the same reason (import + `@workflowbuilder/ui/styles.css` once if you also want the reset and + typography); +- copying a single built file out of the package keeps it self-contained. + +Do **not** replace the stamped statement with a shared `@import`: a relative +import breaks silently when a file is copied out alone, and constructed +stylesheets ignore `@import` entirely. + +`scripts/check-built-css.ts` fails the build if any dist stylesheet stops +leading with the statement, uses an unknown layer name, or ships rules outside +`@layer`. + +## Variable defaults + +`:root` blocks with variable defaults (`--ax-public-*` in component sources, +the `--ax-*` design tokens copied from `@workflowbuilder/ui-tokens`) ship +inside `ui.base`. Layered declarations lose to any unlayered consumer CSS, so +a plain `:root { --ax-…: … }` override in an app always wins - even when a +lazily loaded component stylesheet arrives after it. Sources keep the plain +top-level `:root { … }` convention; the build does the wrapping +(`postcss-layer-root-defaults.mts` for component CSS, the token-copy transform +in `vite.config.mts` for the generated token files). + +## box-sizing + +`box-sizing: border-box` is injected into every styling rule of every +`*.module.css` at build time by `postcss-box-sizing.mts` - you will not find +the declarations in source. Rationale and rejected alternatives: +`postcss-box-sizing.decision-log.md`. + +## Third-party CSS + +Stylesheets we don't author join `ui.base` at import time, e.g. +`@import 'react-day-picker/style.css' layer(ui.base);` (see +`date-picker/variables.css`). Their defaults then lose to our `ui.component` +theming by layer order alone - no unlayered escape hatches, no specificity +arithmetic against upstream selectors. diff --git a/packages/ui/eslint.config.mjs b/packages/ui/eslint.config.mjs new file mode 100644 index 000000000..05585016e --- /dev/null +++ b/packages/ui/eslint.config.mjs @@ -0,0 +1,44 @@ +import baseEslintConfig from '../../eslint.config.mjs'; +import pluginReact from 'eslint-plugin-react'; +import pluginHooks from 'eslint-plugin-react-hooks'; + +const rules = { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + }, + ], + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/function-component-definition': [ + 'error', + { + namedComponents: 'function-declaration', + }, + ], +}; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...baseEslintConfig, + { files: ['**/*.{ts,tsx}'] }, + pluginReact.configs.flat.recommended, + pluginReact.configs.flat['jsx-runtime'], + { + plugins: { + 'react-hooks': pluginHooks, + }, + rules: { + ...pluginHooks.configs.recommended.rules, + ...rules, + }, + settings: { + react: { + version: 'detect', + }, + }, + }, +]; diff --git a/packages/ui/lint-staged.config.mjs b/packages/ui/lint-staged.config.mjs new file mode 100644 index 000000000..63809e0a3 --- /dev/null +++ b/packages/ui/lint-staged.config.mjs @@ -0,0 +1 @@ +export { default } from '../../lint-staged.config.mjs'; diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 000000000..2edd03db7 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,98 @@ +{ + "name": "@workflowbuilder/ui", + "type": "module", + "version": "2.0.0", + "description": "Workflow Builder's component library: accessible, themeable React UI primitives (button, input, select, modal, menu, date picker, switch, tooltip, and more) built on Base UI, plus diagram building blocks for node/edge UIs.", + "keywords": [ + "react", + "ui", + "components", + "component-library", + "base-ui", + "design-system", + "accessible", + "typescript", + "workflow-builder" + ], + "author": "Synergy Codes", + "license": "Apache-2.0", + "homepage": "https://github.com/synergycodes/workflowbuilder", + "repository": { + "type": "git", + "url": "git+https://github.com/synergycodes/workflowbuilder.git", + "directory": "packages/ui" + }, + "bugs": { + "url": "https://github.com/synergycodes/workflowbuilder/issues" + }, + "scripts": { + "build": "vite build && pnpm check:built-css", + "dev": "vite build --watch", + "prepare": "pnpm build", + "check:built-css": "tsx ./scripts/check-built-css.ts", + "lint": "eslint", + "lint:fix": "eslint --fix", + "typecheck": "tsc --noEmit --pretty && tsc --noEmit --pretty -p tsconfig.node.json", + "test": "vitest run", + "build:stats": "BUNDLE_STATS=1 vite build" + }, + "module": "./dist/index.js", + "files": [ + "dist", + "README.md", + "LICENSE", + "CHANGELOG.md", + "css-layers.md" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./*": { + "types": "./dist/components/*/index.d.ts", + "import": "./dist/*.js" + }, + "./index.css": "./dist/index.css", + "./styles.css": "./dist/styles.css", + "./tokens.css": "./dist/tokens.css" + }, + "dependencies": { + "@base-ui/react": "catalog:", + "@phosphor-icons/react": "catalog:", + "react-textarea-autosize": "^8.5.6" + }, + "devDependencies": { + "@types/react": "catalog:", + "@types/react-dom": "^19.1.0", + "@workflowbuilder/ui-tokens": "workspace:*", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.2.0", + "postcss": "^8.5.3", + "react": "catalog:", + "react-day-picker": "^9.14.0", + "react-dom": "catalog:", + "rollup-plugin-visualizer": "^7.0.1", + "tsx": "^4.19.3", + "typescript": "~5.6.3", + "vite": "^6.2.2", + "vite-plugin-dts": "^4.5.3", + "vite-plugin-lib-inject-css": "^2.2.1", + "vite-plugin-static-copy": "^2.3.1", + "vitest": "^3.0.4" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "publishConfig": { + "access": "public" + }, + "sideEffects": [ + "**/*.css" + ] +} diff --git a/packages/ui/postcss-box-sizing.decision-log.md b/packages/ui/postcss-box-sizing.decision-log.md new file mode 100644 index 000000000..660f029d2 --- /dev/null +++ b/packages/ui/postcss-box-sizing.decision-log.md @@ -0,0 +1,64 @@ +# Keep build-time per-rule box-sizing injection + +### Title: Keep the postcss box-sizing plugin over lint-based or selector-based alternatives + +### Proposed by: Jan Librowski + +### Date: 07.08.2026 + +## Context + +Every element the library styles must use `box-sizing: border-box`, without +affecting any element the consumer owns. Today `postcss-box-sizing.mts` +injects the declaration into every styling rule of every `*.module.css` at +build time (~330 copies in `dist/index.css`, mostly absorbed by gzip). An +adversarial review of the PR #44 fix stack surfaced the plugin's costs - it +had shipped type-unsafe code in `pnpm typecheck`'s blind spot and used to +inject into `@keyframes` steps - which raised the question whether the +mechanism should be replaced with something declarative, e.g. a lint rule +requiring the declaration in each component's root class. + +## Decision + +Keep the build-time plugin. It stays typed, spec-covered +(`postcss-box-sizing.spec.mts`), and injects only inside an allowlist of +known styling contexts (`media` / `supports` / `container` / `layer`), so +unknown at-rules fail toward a missed redundant declaration instead of +polluted semantics. The mechanism is documented in `css-layers.md` so the +injected declarations are not a surprise when reading `dist`. + +## Alternative Options Considered + +- **Lint rule: require `box-sizing` on each component's root class.** + Rejected: `box-sizing` does not inherit. A declaration on the root leaves + every descendant at `content-box`; the rule would enforce a false sense of + safety. +- **Scoped universal selector per component (`.root, .root *`).** Rejected: + Base UI popups render through portals outside the component's subtree + (e.g. the date-picker calendar mounts under `body`), so descendant + selectors miss exactly the elements most likely to regress. Per-rule + injection is portal-proof because it rides on the class rules themselves. +- **Global reset in the base layer (`@layer ui.base { * { box-sizing: +border-box } }`).** Rejected: a cascade layer lowers priority in + conflicts but does not limit reach - consumer elements with no competing + declaration would receive the library's value. A library must not style + elements it does not own. +- **Lint rule: require the explicit declaration in every source rule.** + Rejected: produces byte-identical output while moving ~330 repeated + declarations into hand-written source - pure authoring noise with no + semantic gain over build-time injection. + +## Consequences + +- The injection stays invisible in source; `css-layers.md` carries the + pointer to this mechanism. +- `STYLING_CONTEXTS` must be consciously extended when the codebase adopts a + new rule-carrying at-rule (e.g. `@scope`); the spec pins the skip-unknown + behavior so a regression to blanket injection fails tests. +- The plugin remains a root-level `.mts` file, outside the tsconfig `include` + scope - type errors there are invisible to `pnpm typecheck` unless the + program is extended to cover root-level build files. + +## Status + +accepted diff --git a/packages/ui/postcss-box-sizing.mts b/packages/ui/postcss-box-sizing.mts new file mode 100644 index 000000000..2d3acfb75 --- /dev/null +++ b/packages/ui/postcss-box-sizing.mts @@ -0,0 +1,72 @@ +import type { ChildNode, Document, Plugin, Root, Rule } from 'postcss'; + +/** + * PostCSS plugin that adds `box-sizing: border-box` to every styling rule + * in CSS Module files (*.module.css). This ensures all library + * components use border-box sizing without leaking styles to users. + */ + +// Inject only inside contexts we KNOW are plain styling (an allowlist, not a +// @keyframes blocklist): CSS keeps adding new at-rules, and for an unknown one +// a missed box-sizing is easy to spot, while a wrongly injected one +// (e.g. inside @keyframes) silently becomes animated. +const STYLING_CONTEXTS = new Set(['media', 'supports', 'container', 'layer']); + +type Ancestor = Document | Root | ChildNode; + +function isPlainStylingContext(rule: Rule): boolean { + let parent: Ancestor | undefined = rule.parent; + + while (parent && parent.type !== 'root') { + const isNestedSelector = parent.type === 'rule'; + const isStylingAtRule = parent.type === 'atrule' && STYLING_CONTEXTS.has(parent.name); + + if (!isNestedSelector && !isStylingAtRule) return false; + parent = parent.parent; + } + + return true; +} + +// `:root` blocks hold design tokens, not element styling - also when grouped +// (`:root, .fallback`) or qualified (`:root[data-theme='dark']`). +function targetsRoot(rule: Rule): boolean { + return rule.selectors.some((selector) => selector.trim().startsWith(':root')); +} + +export function boxSizingPlugin(): Plugin { + return { + postcssPlugin: 'postcss-box-sizing', + prepare() { + return { + OnceExit(root) { + const file = root.source?.input?.file ?? ''; + if (!file.endsWith('.module.css')) return; + + root.walkRules((rule) => { + if (targetsRoot(rule)) return; + if (!isPlainStylingContext(rule)) return; + + const hasDeclarations = rule.nodes?.some( + (node) => node.type === 'decl', + ); + + // Skip rules that only contain nested rules (no declarations). + // Adding box-sizing to such container rules would leak to unintended elements + if (!hasDeclarations) return; + + const hasBoxSizing = rule.nodes?.some( + (node) => node.type === 'decl' && node.prop === 'box-sizing', + ); + + if (!hasBoxSizing) { + rule.prepend({ prop: 'box-sizing', value: 'border-box' }); + } + }); + }, + }; + }, + }; +} + +boxSizingPlugin.postcss = true; diff --git a/packages/ui/postcss-box-sizing.spec.mts b/packages/ui/postcss-box-sizing.spec.mts new file mode 100644 index 000000000..e17bb444e --- /dev/null +++ b/packages/ui/postcss-box-sizing.spec.mts @@ -0,0 +1,76 @@ +import postcss from 'postcss'; +import { describe, expect, it } from 'vitest'; + +import { boxSizingPlugin } from './postcss-box-sizing.mts'; + +async function run(css: string, from = '/fake/component.module.css'): Promise { + const result = await postcss([boxSizingPlugin()]).process(css, { from }); + return result.css; +} + +describe('postcss-box-sizing', () => { + it('prepends box-sizing to a plain styling rule', async () => { + const output = await run('.button { color: red }'); + expect(output).toBe('.button { box-sizing: border-box; color: red }'); + }); + + it('keeps an explicit box-sizing untouched', async () => { + const output = await run('.button { box-sizing: content-box }'); + expect(output).toBe('.button { box-sizing: content-box }'); + }); + + it('injects inside known styling at-rules, however nested', async () => { + const output = await run( + '@layer ui.component { @media (min-width: 1px) { .button { color: red } } }', + ); + expect(output).toContain('box-sizing: border-box'); + }); + + it('never touches @keyframes steps', async () => { + const output = await run('@keyframes flash { 0% { opacity: 0 } to { opacity: 1 } }'); + expect(output).not.toContain('box-sizing'); + }); + + it('never touches @keyframes steps nested inside styling at-rules', async () => { + const output = await run( + '@layer ui.component { @media (min-width: 1px) { @keyframes flash { 0% { opacity: 0 } } } }', + ); + expect(output).not.toContain('box-sizing'); + }); + + it('never touches vendor-prefixed @keyframes steps', async () => { + const output = await run('@-webkit-keyframes flash { 0% { opacity: 0 } }'); + expect(output).not.toContain('box-sizing'); + }); + + it('skips :root also when grouped or qualified', async () => { + const output = await run(":root, .fallback { --x: 1px }\n:root[data-theme='dark'] { --y: 2px }"); + expect(output).not.toContain('box-sizing'); + }); + + it('skips at-rules outside the styling allowlist', async () => { + const output = await run('@starting-style { .button { opacity: 0 } }'); + expect(output).not.toContain('box-sizing'); + }); + + it('skips :root token blocks', async () => { + const output = await run(':root { --x: 1px }'); + expect(output).not.toContain('box-sizing'); + }); + + it('leaves non-module stylesheets alone', async () => { + const output = await run('.button { color: red }', '/fake/global.css'); + expect(output).toBe('.button { color: red }'); + }); + + // The plugin decides per rule; it cannot know that a state selector targets + // the same element as an opted-out base rule (undecidable from CSS alone). + // Contract: a content-box opt-out must be repeated in every state rule of + // that element, or the state rule silently becomes border-box. + it('injects into a state rule even when the base rule opts out', async () => { + const output = await run('.handle { box-sizing: content-box }\n.handle:hover { width: 1rem }'); + expect(output).toBe( + '.handle { box-sizing: content-box }\n.handle:hover { box-sizing: border-box; width: 1rem }', + ); + }); +}); diff --git a/packages/ui/postcss-layer-root-defaults.mts b/packages/ui/postcss-layer-root-defaults.mts new file mode 100644 index 000000000..2fc3defcb --- /dev/null +++ b/packages/ui/postcss-layer-root-defaults.mts @@ -0,0 +1,42 @@ +import type { Plugin, Rule } from 'postcss'; + +/** + * PostCSS plugin that wraps top-level `:root` blocks (variable defaults) in + * `@layer ui.base`. + * + * Layered declarations lose to unlayered CSS regardless of source order, so a + * consumer's `:root { --ax-public-…: … }` override always beats the shipped + * default - even when a lazily loaded component stylesheet arrives after the + * override. Unlayered defaults would instead win or lose by load order. + * + * Applied at build time (like the @layer order stamp) so sources keep the + * plain `:root { … }` authoring convention. + */ + +// Also grouped (`:root, .fallback`) or qualified (`:root[data-theme='dark']`). +function targetsRoot(rule: Rule): boolean { + return rule.selectors.some((selector) => selector.trim().startsWith(':root')); +} + +export function layerRootDefaultsPlugin(): Plugin { + return { + postcssPlugin: 'postcss-layer-root-defaults', + prepare() { + return { + OnceExit(root, { atRule }) { + const topLevelRootRules = root.nodes.filter( + (node): node is Rule => node.type === 'rule' && targetsRoot(node), + ); + + for (const rule of topLevelRootRules) { + const layer = atRule({ name: 'layer', params: 'ui.base', source: rule.source }); + rule.replaceWith(layer); + layer.append(rule); + } + }, + }; + }, + }; +} + +layerRootDefaultsPlugin.postcss = true; diff --git a/packages/ui/postcss-layer-root-defaults.spec.mts b/packages/ui/postcss-layer-root-defaults.spec.mts new file mode 100644 index 000000000..50bcd5be1 --- /dev/null +++ b/packages/ui/postcss-layer-root-defaults.spec.mts @@ -0,0 +1,39 @@ +import postcss from 'postcss'; +import { describe, expect, it } from 'vitest'; + +import { layerRootDefaultsPlugin } from './postcss-layer-root-defaults.mts'; + +async function run(css: string): Promise { + const result = await postcss([layerRootDefaultsPlugin()]).process(css, { + from: '/fake/component.module.css', + }); + return result.css; +} + +describe('postcss-layer-root-defaults', () => { + it('wraps a top-level :root block in @layer ui.base', async () => { + const output = await run(':root { --x: 1px }'); + expect(output.replaceAll(/\s+/g, ' ')).toBe('@layer ui.base { :root { --x: 1px }}'); + }); + + it('wraps grouped and qualified :root selectors', async () => { + const output = await run(":root, .fallback { --x: 1px }\n:root[data-theme='dark'] { --y: 2px }"); + expect(output.match(/@layer ui\.base/g)).toHaveLength(2); + }); + + it('keeps multiple blocks in source order', async () => { + const output = await run(':root { --a: 1px }\n.button { color: red }\n:root { --b: 2px }'); + expect(output.indexOf('--a')).toBeLessThan(output.indexOf('.button')); + expect(output.indexOf('.button')).toBeLessThan(output.indexOf('--b')); + }); + + it('leaves :root already inside @layer alone', async () => { + const input = '@layer ui.component { :root { --x: 1px } }'; + expect(await run(input)).toBe(input); + }); + + it('leaves non-root rules alone', async () => { + const input = '.button { color: red }'; + expect(await run(input)).toBe(input); + }); +}); diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts new file mode 100644 index 000000000..79710c167 --- /dev/null +++ b/packages/ui/scripts/check-built-css.ts @@ -0,0 +1,299 @@ +/** + * Guards against known bug classes in built CSS output. + * + * Runs after `vite build` so a regression fails the release even if it slips + * past review or a build-tool transform introduces it. + * + * 1. `var()`'s first argument must be a `` (a dashed-ident) - + * never another function, an undashed name, or nothing. Browsers silently + * invalidate the declaration and fall back, which is how WB-222 shipped a + * wrong snackbar icon color (`var(var(--foo))`). + * 2. Every rule must live inside an `@layer` block - including `:root` variable + * defaults (layered defaults lose to any unlayered consumer override, which + * is the override contract). Unlayered CSS beats layered CSS regardless of + * specificity, so a rule that escapes `ui.base` / `ui.component` silently + * wins the cascade - this is how WB-190 shipped decision-node ports + * collapsed to ~5px (see `css-layers.md`). + * 3. Every stylesheet must LEAD with the full `@layer` order statement. The first + * use of a layer name fixes the order, so a component stylesheet that loads + * before the statement inverts the cascade (ui.base beats ui.component) - + * `combine-css-bundle.mts` stamps the statement into every file; this check + * keeps the stamp honest. + * 4. Only the layer names declared in `src/styles/layers.css` may appear. An + * unknown name (a typo) lands AFTER the declared order and silently wins + * the cascade. + * 5. Every `*.css` entry in package.json `exports` must exist in dist, and no + * dist stylesheet may use `@import` - a relative import breaks silently when + * a file is copied out alone, and constructed stylesheets ignore imports. + * + * These are the only lines of defense for these bug classes today; source-level lint + * rules would catch some of them earlier but none is configured yet. + * + * Exits non-zero on any match. + */ +import { existsSync, globSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import postcss, { type AtRule, type ChildNode } from 'postcss'; + +const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); +const packageDirectory = path.resolve(currentDirectory, '..'); +const distributionDirectory = path.resolve(packageDirectory, 'dist'); + +type Hit = { line: number; column: number; snippet: string }; +type FailureReport = { file: string; hits: Hit[] }; + +const layerOrderStatement = postcss + .parse(readFileSync(path.resolve(packageDirectory, 'src/styles/layers.css'), 'utf8')) + .nodes.find((node): node is AtRule => node.type === 'atrule' && node.name === 'layer' && node.nodes === undefined); +if (!layerOrderStatement) { + throw new Error('src/styles/layers.css must contain a bare `@layer a, b;` order statement'); +} +const KNOWN_LAYERS = layerOrderStatement.params.split(',').map((name) => name.trim()); +const KNOWN_LAYER_SET = new Set(KNOWN_LAYERS); + +function locate(content: string, index: number): { line: number; column: number } { + const upTo = content.slice(0, index); + const line = upTo.split('\n').length; + const column = index - upTo.lastIndexOf('\n'); + return { line, column }; +} + +function snippetAt(content: string, index: number): string { + return content.slice(Math.max(0, index - 12), index + 48).replaceAll(/\s+/g, ' '); +} + +function hitFor(node: ChildNode | undefined): Hit { + if (!node) return { line: 0, column: 0, snippet: '(no CSS rules found)' }; + + const start = node.source?.start; + return { + line: start?.line ?? 0, + column: start?.column ?? 0, + snippet: node.toString().slice(0, 60).replaceAll(/\s+/g, ' '), + }; +} + +// --- Check 1: var() first argument is a dashed ident -------------------------- + +const malformedVariablePattern = /var\(\s*(?!--)/g; + +function checkVariableFirstArgument(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; + + for (const file of files) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits: Hit[] = []; + + for (const match of content.matchAll(malformedVariablePattern)) { + const { line, column } = locate(content, match.index); + hits.push({ line, column, snippet: snippetAt(content, match.index) }); + } + + if (hits.length > 0) failures.push({ file, hits }); + } + + return failures; +} + +// --- Check 2: no rule outside @layer ---------------------------------------- + +function findTopLevelViolations(content: string): Hit[] { + const hits: Hit[] = []; + + for (const node of postcss.parse(content).nodes) { + switch (node.type) { + case 'atrule': { + const isStatement = node.nodes === undefined; + if (node.name === 'layer' || isStatement) break; + hits.push(hitFor(node)); + break; + } + case 'rule': { + hits.push(hitFor(node)); + break; + } + default: { + break; + } + } + } + + return hits; +} + +function checkLayerCoverage(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; + + for (const file of files) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits = findTopLevelViolations(content); + + if (hits.length > 0) failures.push({ file, hits }); + } + + return failures; +} + +// --- Check 3: the layer-order statement leads every file ---------------------- + +function isOrderStatement(node: ChildNode | undefined): boolean { + return ( + node?.type === 'atrule' && + node.name === 'layer' && + node.nodes === undefined && + node.params + .split(',') + .map((name) => name.trim()) + .join(',') === KNOWN_LAYERS.join(',') + ); +} + +function checkOrderStatementFirst(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; + + for (const file of files) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const first = postcss.parse(content).nodes.find((node) => node.type !== 'comment'); + + if (!isOrderStatement(first)) failures.push({ file, hits: [hitFor(first)] }); + } + + return failures; +} + +// --- Check 4: only known layer names ------------------------------------------ + +function checkKnownLayerNames(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; + + for (const file of files) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits: Hit[] = []; + + postcss.parse(content).walkAtRules('layer', (atRule) => { + const names = atRule.params.split(',').map((name) => name.trim()); + if (names.some((name) => !KNOWN_LAYER_SET.has(name))) hits.push(hitFor(atRule)); + }); + + if (hits.length > 0) failures.push({ file, hits }); + } + + return failures; +} + +// --- Check 5: exported entrypoints exist, dist CSS is import-free -------------- + +// Walks an exports-map value, collecting every string target ending in .css - +// covers plain strings and conditional-export objects ({ import, require, +// default, ... }) at any nesting depth. +function collectCssTargets(value: unknown, into: string[]): void { + if (typeof value === 'string') { + if (value.endsWith('.css')) into.push(value); + return; + } + if (value && typeof value === 'object') { + for (const nested of Object.values(value)) collectCssTargets(nested, into); + } +} + +function checkPublishedSurface(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; + + const packageJson = JSON.parse(readFileSync(path.resolve(packageDirectory, 'package.json'), 'utf8')) as { + exports?: Record; + }; + for (const [specifier, target] of Object.entries(packageJson.exports ?? {})) { + const cssTargets: string[] = []; + collectCssTargets(target, cssTargets); + + for (const cssTarget of cssTargets) { + if (!existsSync(path.resolve(packageDirectory, cssTarget))) { + failures.push({ + file: cssTarget, + hits: [{ line: 0, column: 0, snippet: `missing file for exports["${specifier}"]` }], + }); + } + } + } + + if (files.length === 0) { + failures.push({ + file: '(dist)', + hits: [{ line: 0, column: 0, snippet: 'no CSS emitted at all' }], + }); + } + + for (const file of files) { + const hits: Hit[] = []; + + postcss.parse(readFileSync(path.resolve(distributionDirectory, file), 'utf8')).walkAtRules('import', (atRule) => { + hits.push(hitFor(atRule)); + }); + + if (hits.length > 0) failures.push({ file, hits }); + } + + return failures; +} + +// --- Run all checks --------------------------------------------------------- + +function report(title: string, failures: FailureReport[], hint: string): boolean { + if (failures.length === 0) { + console.log(`✔ ${title}`); + return true; + } + + const total = failures.reduce((n, f) => n + f.hits.length, 0); + console.error(`\n✖ Found ${total} issue(s): ${title}\n`); + for (const { file, hits } of failures) { + console.error(` packages/ui/dist/${file}`); + for (const { line, column, snippet } of hits) { + console.error(` ${line}:${column} …${snippet}…`); + } + } + console.error(`\n${hint}\n`); + return false; +} + +const files = globSync('**/*.css', { cwd: distributionDirectory }); + +const results = [ + report( + 'Built CSS: every var() takes a --custom-property name', + checkVariableFirstArgument(files), + 'The first argument of var() must be a --custom-property name - browsers silently ' + + 'discard the whole declaration otherwise. See WB-222.', + ), + report( + 'Built CSS: every rule sits inside an @layer block', + checkLayerCoverage(files), + 'Unlayered CSS wins the cascade over layered CSS regardless of specificity - wrap the ' + + 'source rule in `@layer ui.base { ... }` or `@layer ui.component { ... }` (see ' + + 'packages/ui/css-layers.md). `:root` defaults are wrapped by the build ' + + '(postcss-layer-root-defaults.mts); a hit here means CSS bypassed that pipeline.', + ), + report( + 'Built CSS: every file leads with the @layer order statement', + checkOrderStatementFirst(files), + 'Each dist stylesheet must begin with the full order statement from src/styles/layers.css - ' + + 'combine-css-bundle.mts stamps it; a missing stamp reintroduces the inverted-cascade bug.', + ), + report( + `Built CSS: only known layer names (${KNOWN_LAYERS.join(', ')})`, + checkKnownLayerNames(files), + 'An unknown layer name lands AFTER the declared order and silently wins the cascade - fix the typo.', + ), + report( + 'Built CSS: exported entrypoints exist and no stylesheet uses @import', + checkPublishedSurface(files), + 'package.json exports must point at real files, and dist CSS must be self-contained - ' + + 'a relative @import breaks silently when a file is copied out of the package.', + ), +]; + +if (results.includes(false)) { + process.exitCode = 1; +} diff --git a/packages/ui/src/components/accordion/accordion.module.css b/packages/ui/src/components/accordion/accordion.module.css new file mode 100644 index 000000000..8933b740a --- /dev/null +++ b/packages/ui/src/components/accordion/accordion.module.css @@ -0,0 +1,23 @@ +@layer ui.component { + .accordion { + display: flex; + flex-direction: column; + + > hr { + margin: 1.25rem 0 0.25rem 0; + align-self: center; + } + + .header { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + user-select: none; + } + + .inner-content { + padding: 12px 0; + } + } +} diff --git a/packages/ui/src/components/accordion/accordion.tsx b/packages/ui/src/components/accordion/accordion.tsx new file mode 100644 index 000000000..a0da7356d --- /dev/null +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -0,0 +1,79 @@ +import clsx from 'clsx'; +import { forwardRef, useState } from 'react'; + +import styles from './accordion.module.css'; + +import { WithIcon } from '../../shared/types/with-icon'; +import { Collapsible } from '../collapsible/collapsible'; +import { Separator } from '../separator/separator'; + +export type AccordionProps = React.HTMLAttributes & + WithIcon & { + /** + * Text displayed in the header + */ + label: string; + + /** + * Contents of the collapsible section + */ + children: React.ReactNode; + + /** + * True if not collapsed + */ + isOpen?: boolean; + + /** + * Callback run when the open state changes + */ + onToggleOpen?: (isOpen: boolean) => void; + + /** + * Initial open state + */ + defaultOpen?: boolean; + }; + +/** + * An interactive UI component that lets users toggle the visibility of content. + * The content section can be expanded to reveal details and collapsed to hide them, + * keeping information organized and saving space. Commonly used in FAQs, settings panels, + * and documentation to present layered content efficiently. + */ +export const Accordion = forwardRef( + ({ label, children, onToggleOpen, isOpen: isOpenProperty, defaultOpen = true, className, ...props }, ref) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + + const isExpanded = isOpenProperty ?? isOpen; + + function onExpandCollapse() { + const newValue = !isExpanded; + + setIsOpen(newValue); + onToggleOpen?.(newValue); + } + + return ( + // The arrow bubbles into the header's onClick, so it must not fire onToggle too. + +
+
+ {label} + +
+ +
{children}
+
+ +
+
+ ); + }, +); diff --git a/packages/ui/src/components/accordion/index.ts b/packages/ui/src/components/accordion/index.ts new file mode 100644 index 000000000..ce956fe2e --- /dev/null +++ b/packages/ui/src/components/accordion/index.ts @@ -0,0 +1 @@ +export * from './accordion'; diff --git a/packages/ui/src/components/avatar/avatar.module.css b/packages/ui/src/components/avatar/avatar.module.css new file mode 100644 index 000000000..76e1dfa12 --- /dev/null +++ b/packages/ui/src/components/avatar/avatar.module.css @@ -0,0 +1,43 @@ +:root { + --ax-public-avatar-border-color: var(--ax-input-stroke-primary-default); /* missing token */ + --ax-public-avatar-background-color: var(--ax-colors-blue-500); /* missing token */ + --ax-public-avatar-border-size: 0.0625rem; +} + +@layer ui.component { + .container { + border: var(--ax-public-avatar-border-size) solid var(--ax-public-avatar-border-color); + background-color: var(--ax-public-avatar-background-color); + border-radius: 50%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + + &.extra-large { + width: calc(2.5rem + 2 * var(--ax-public-avatar-border-size)); + height: calc(2.5rem + 2 * var(--ax-public-avatar-border-size)); + } + + &.large { + width: calc(2rem + 2 * var(--ax-public-avatar-border-size)); + height: calc(2rem + 2 * var(--ax-public-avatar-border-size)); + } + + &.medium { + width: calc(1.5rem + 2 * var(--ax-public-avatar-border-size)); + height: calc(1.5rem + 2 * var(--ax-public-avatar-border-size)); + } + + &.small { + width: calc(1.125rem + 2 * var(--ax-public-avatar-border-size)); + height: calc(1.125rem + 2 * var(--ax-public-avatar-border-size)); + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } +} diff --git a/packages/ui/src/components/avatar/avatar.tsx b/packages/ui/src/components/avatar/avatar.tsx new file mode 100644 index 000000000..baba0cb48 --- /dev/null +++ b/packages/ui/src/components/avatar/avatar.tsx @@ -0,0 +1,31 @@ +import clsx from 'clsx'; + +import styles from './avatar.module.css'; + +type Props = { + /** + * Provide to use it as alt of the image for better a11y + */ + username: string; + /** + * Image URL + */ + imageUrl?: string; + /** + * Size of the circle container + */ + size?: Size; +}; + +type Size = 'extra-large' | 'large' | 'medium' | 'small'; + +/** + * Component for displaying user avatars with various sizes + */ +export function Avatar({ imageUrl, username, size = 'extra-large' }: Props) { + return ( +
+ {username} +
+ ); +} diff --git a/packages/ui/src/components/avatar/index.ts b/packages/ui/src/components/avatar/index.ts new file mode 100644 index 000000000..a7424ca31 --- /dev/null +++ b/packages/ui/src/components/avatar/index.ts @@ -0,0 +1 @@ +export * from './avatar'; diff --git a/packages/ui/src/components/button/base-button/base-button.module.css b/packages/ui/src/components/button/base-button/base-button.module.css new file mode 100644 index 000000000..a994937b8 --- /dev/null +++ b/packages/ui/src/components/button/base-button/base-button.module.css @@ -0,0 +1,34 @@ +:root { + --ax-public-button-background-color-disabled: var(--ax-button-primary-bg-disabled); + --ax-public-button-focus-visible-box-shadow-color: var(--ax-txt-primary-default); + --ax-public-button-inner-border-color: var(--ax-ui-bg-primary-default); + --ax-public-button-color: var(--ax-txt-primary-white); +} + +@layer ui.base { + .button { + display: flex; + align-items: center; + justify-content: center; + position: relative; + color: var(--ax-public-button-color); + border: unset; + cursor: pointer; + + &:focus-visible { + box-shadow: + 0px 0px 0px 2px var(--ax-public-button-focus-visible-box-shadow-color), + inset 0 0 0 1px var(--ax-public-button-inner-border-color); + outline: none; + } + + &:not(:hover) { + transition: all var(--ax-public-transition); + } + + &:disabled { + background-color: var(--ax-public-button-background-color-disabled); + cursor: auto; + } + } +} diff --git a/packages/ui/src/components/button/base-button/base-button.tsx b/packages/ui/src/components/button/base-button/base-button.tsx new file mode 100644 index 000000000..f553902b8 --- /dev/null +++ b/packages/ui/src/components/button/base-button/base-button.tsx @@ -0,0 +1,38 @@ +import { Tooltip } from '@ui/components/tooltip/tooltip'; +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import buttonStyles from './base-button.module.css'; + +import { BaseButtonProps } from '../types'; + +type Props = { + /** Class name meant to be used by parent components using directly */ + styles: string; + children: React.ReactNode; +} & BaseButtonProps; + +export const BaseButton = forwardRef( + ({ children, styles, className, tooltip, disabled, type = 'button', tooltipType = 'default', ...props }, ref) => { + const button = ( + + ); + + return tooltip && !disabled ? ( + + {button} + {tooltip} + + ) : ( + button + ); + }, +); diff --git a/packages/ui/src/components/button/guards.spec.ts b/packages/ui/src/components/button/guards.spec.ts new file mode 100644 index 000000000..a9738caf1 --- /dev/null +++ b/packages/ui/src/components/button/guards.spec.ts @@ -0,0 +1,85 @@ +import { type ReactNode, createElement } from 'react'; + +import { hasChildrenWithStringAndIcons, hasIconChildrenOnly, hasStringChildrenOnly } from './guards'; + +const icon = createElement('span', null, 'icon'); +const otherIcon = createElement('svg'); + +describe('hasIconChildrenOnly', () => { + it('returns true for a single element child', () => { + expect(hasIconChildrenOnly({ children: icon })).toBe(true); + }); + + it('returns true for a function child', () => { + // A render-prop function is not a valid `ReactNode`, but the guard + // accepts it at runtime via a `typeof === 'function'` check. + const functionChild = (() => null) as unknown as ReactNode; + expect(hasIconChildrenOnly({ children: functionChild })).toBe(true); + }); + + it('returns false for a string child', () => { + expect(hasIconChildrenOnly({ children: 'label' })).toBe(false); + }); + + it('returns false for an array of children', () => { + expect(hasIconChildrenOnly({ children: ['label', icon] })).toBe(false); + }); + + it('returns false for number/boolean/null children', () => { + expect(hasIconChildrenOnly({ children: 42 })).toBe(false); + expect(hasIconChildrenOnly({ children: true })).toBe(false); + expect(hasIconChildrenOnly({ children: null })).toBe(false); + }); +}); + +describe('hasChildrenWithStringAndIcons', () => { + it('returns true for [string, element]', () => { + expect(hasChildrenWithStringAndIcons({ children: ['label', icon] })).toBe(true); + }); + + it('returns true for [element, string]', () => { + expect(hasChildrenWithStringAndIcons({ children: [icon, 'label'] })).toBe(true); + }); + + it('returns true for two elements', () => { + expect(hasChildrenWithStringAndIcons({ children: [icon, otherIcon] })).toBe(true); + }); + + it('returns false for an array shorter than 2', () => { + expect(hasChildrenWithStringAndIcons({ children: [icon] })).toBe(false); + }); + + it('returns false for a single string child (not an array)', () => { + expect(hasChildrenWithStringAndIcons({ children: 'label' })).toBe(false); + }); + + it('returns false for a single element child (not an array)', () => { + expect(hasChildrenWithStringAndIcons({ children: icon })).toBe(false); + }); + + it('returns false when an entry is number/boolean/null', () => { + expect(hasChildrenWithStringAndIcons({ children: [42, icon] })).toBe(false); + expect(hasChildrenWithStringAndIcons({ children: [icon, null] })).toBe(false); + expect(hasChildrenWithStringAndIcons({ children: [true, false] })).toBe(false); + }); +}); + +describe('hasStringChildrenOnly', () => { + it('returns true for a string child', () => { + expect(hasStringChildrenOnly({ children: 'label' })).toBe(true); + }); + + it('returns false for an element child', () => { + expect(hasStringChildrenOnly({ children: icon })).toBe(false); + }); + + it('returns false for an array child', () => { + expect(hasStringChildrenOnly({ children: ['label', icon] })).toBe(false); + }); + + it('returns false for number/boolean/null children', () => { + expect(hasStringChildrenOnly({ children: 42 })).toBe(false); + expect(hasStringChildrenOnly({ children: true })).toBe(false); + expect(hasStringChildrenOnly({ children: null })).toBe(false); + }); +}); diff --git a/packages/ui/src/components/button/guards.ts b/packages/ui/src/components/button/guards.ts new file mode 100644 index 000000000..6ba289aa2 --- /dev/null +++ b/packages/ui/src/components/button/guards.ts @@ -0,0 +1,18 @@ +import { PropsWithChildren, isValidElement } from 'react'; + +export function hasIconChildrenOnly(props: PropsWithChildren): props is PropsWithChildren { + return isValidElement(props.children) || typeof props.children === 'function'; +} + +export function hasChildrenWithStringAndIcons(props: PropsWithChildren): props is PropsWithChildren { + return ( + Array.isArray(props.children) && + props.children.length >= 2 && + (typeof props.children[0] === 'string' || isValidElement(props.children[0])) && + (typeof props.children[1] === 'string' || isValidElement(props.children[1])) + ); +} + +export function hasStringChildrenOnly(props: PropsWithChildren): props is PropsWithChildren { + return typeof (props as PropsWithChildren).children === 'string'; +} diff --git a/packages/ui/src/components/button/index.ts b/packages/ui/src/components/button/index.ts new file mode 100644 index 000000000..84da1622e --- /dev/null +++ b/packages/ui/src/components/button/index.ts @@ -0,0 +1,3 @@ +export * from './nav-button/nav-button'; +export * from './regular-button/button'; +export * from './regular-button/types'; diff --git a/packages/ui/src/components/button/nav-button/nav-button.tsx b/packages/ui/src/components/button/nav-button/nav-button.tsx new file mode 100644 index 000000000..33807559d --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-button.tsx @@ -0,0 +1,111 @@ +import clsx from 'clsx'; +import { ReactElement, forwardRef } from 'react'; + +import borderRadiusStyles from '../styles/border-radius.module.css'; +import navBorderRadiusStyles from './styles/nav-button-border-radius.module.css'; +import navButtonStyles from './styles/nav-button.module.css'; + +import { hasChildrenWithStringAndIcons, hasIconChildrenOnly, hasStringChildrenOnly } from '../guards'; +import { NavIconButton, NavIconButtonProps } from './nav-icon-button/nav-icon-button'; +import { NavIconLabelButton, NavIconLabelButtonProps } from './nav-icon-label-button/nav-icon-label-button'; +import { NavLabelButton, NavLabelButtonProps } from './nav-label-button/nav-label-button'; + +type WithRef = T & { + ref?: React.Ref; +}; + +/** + * NavButtonProps defines **discriminated overloads** for the Button component using + * **structural discrimination** rather than a `type` field. + * + * The component dynamically determines which button variant to render based on the + * **structure of the `children` prop**: + * + * - If `children` is a single `string`, it's treated as a **Label Button**. + * - If `children` is a single icon (ReactElement), it's treated as an **Icon Button**. + * - If `children` includes both a string and one or two icons (before/after), + * it's treated as an **Icon Label Button**. + * + * Based on the inferred variant, **only props specific to that variant are allowed**. + * This ensures that incorrect prop combinations (e.g., passing label-specific props + * to an Icon Button) are caught at compile time. + * + * This is intentionally implemented with **overloads** instead of a union type, + * which would incorrectly allow mixing props between types and compromise type safety. + */ +type NavButtonProps = { + (props: WithRef): ReactElement; + (props: WithRef): ReactElement; + (props: WithRef): ReactElement; +}; + +const NavButtonComponent = forwardRef< + HTMLButtonElement, + NavLabelButtonProps | NavIconButtonProps | NavIconLabelButtonProps +>(({ className, isSelected, size = 'medium', ...props }, ref) => { + const buttonProps = { + ref, + ...props, + className: clsx( + borderRadiusStyles[size], + navBorderRadiusStyles[size], + navButtonStyles['nav-button'], + { [navButtonStyles['selected']]: isSelected }, + className, + ), + size, + }; + + if (hasStringChildrenOnly(props)) { + return {props.children}; + } + + if (hasIconChildrenOnly(props)) { + return {props.children}; + } + + if (hasChildrenWithStringAndIcons(props)) { + return {props.children}; + } + + return null; +}); + +/** + * Button is a flexible, and type-safe component that automatically selects + * the correct type (Label Button, Icon Button, or Icon Label Button) based on the + * structure of its `children` prop. + * + * **Automatic Type Selection (Structural Discrimination)** + * The component uses the shape of `children` to infer which button variant to render: + * - **Label Button**: If `children` is a single `string` + * - **Icon Button**: If `children` is a single React element (e.g., an icon) + * - **Icon Label Button**: If `children` is a combination of string + icon(s) + * + * **Type Safety via Overloads** + * Each variant supports its own unique set of props. Thanks to TypeScript overloads, + * only the correct props for a given structure are allowed—invalid combinations + * are caught at compile time. + * + * **How to Use** + * + * ```tsx + * Submit // Label Button + * + * + * + * // Icon Button + * + * + * + * Confirm + * + * // Icon Label Button + * ``` + * + * This approach ensures: + * - Simplified usage with fewer props + * - No accidental mixing of incompatible props + * - Autocomplete and type-checking experience + */ +export const NavButton = NavButtonComponent as NavButtonProps; diff --git a/packages/ui/src/components/button/nav-button/nav-icon-button/nav-button-icon-padding.module.css b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-button-icon-padding.module.css new file mode 100644 index 000000000..04f074757 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-button-icon-padding.module.css @@ -0,0 +1,14 @@ +:root { + --ax-public-button-icon-padding-xx-small: var(--ax-token-spacing-button-xxs-v-pad); + --ax-public-button-icon-padding-xxx-small: var(--ax-token-spacing-button-xxxs-v-pad); +} + +@layer ui.component { + .xx-small { + padding: var(--ax-public-button-icon-padding-xx-small); + } + + .xxx-small { + padding: var(--ax-public-button-icon-padding-xxx-small); + } +} diff --git a/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.module.css b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.module.css new file mode 100644 index 000000000..a837d10f0 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.module.css @@ -0,0 +1,11 @@ +:root { + --ax-public-button-nav-color-hover: var(--ax-nav-button-icon-primary-hover); +} + +@layer ui.component { + button:hover { + &.transparent { + background: transparent; + } + } +} diff --git a/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.tsx b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.tsx new file mode 100644 index 000000000..e0dffbca3 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-button/nav-icon-button.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import borderRadiusStyles from '../../styles/border-radius.module.css'; +import iconPaddingStyles from '../../styles/icon-padding.module.css'; +import iconSizeStyles from '../../styles/icon-size.module.css'; +import navButtonBorderRadiusStyles from '../styles/nav-button-border-radius.module.css'; +import navButtonIconSizeStyles from '../styles/nav-button-icon-size.module.css'; +import navButtonIconPaddingStyles from './nav-button-icon-padding.module.css'; +import navIconButtonStyles from './nav-icon-button.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { IconNode, Shape } from '../../types'; +import { NavBaseButtonProps } from '../types'; + +export type NavIconButtonProps = { + shape?: Shape; + transparent?: boolean; + children: IconNode; +} & NavBaseButtonProps; + +export const NavIconButton = forwardRef( + ({ size = 'medium', shape = 'default', children, transparent, ...props }, ref) => ( + + {children} + + ), +); diff --git a/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-button-gap.module.css b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-button-gap.module.css new file mode 100644 index 000000000..55780c34c --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-button-gap.module.css @@ -0,0 +1,15 @@ +:root { + --ax-public-button-gap-xx-small: var(--ax-token-spacing-button-xxs-v-pad-2) var(--ax-token-spacing-button-xxs-h-pad-2); + --ax-public-button-gap-xxx-small: var(--ax-token-spacing-button-xxxs-v-pad-2) + var(--ax-token-spacing-button-xxxs-h-pad-2); +} + +@layer ui.component { + .xx-small { + gap: var(--ax-public-button-gap-xx-small); + } + + .xxx-small { + gap: var(--ax-public-button-gap-xxx-small); + } +} diff --git a/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button-padding.module.css b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button-padding.module.css new file mode 100644 index 000000000..eb0aef77d --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button-padding.module.css @@ -0,0 +1,16 @@ +:root { + --ax-public-icon-label-button-padding-xx-small: var(--ax-token-spacing-button-xxs-v-pad-2) + var(--ax-token-spacing-button-xxs-h-pad-2); + --ax-public-icon-label-button-padding-xxx-small: var(--ax-token-spacing-button-xxxs-v-pad-2) + var(--ax-token-spacing-button-xxxs-h-pad-2); +} + +@layer ui.component { + .xx-small { + padding: var(--ax-public-icon-label-button-padding-xx-small); + } + + .xxx-small { + padding: var(--ax-public-icon-label-button-padding-xxx-small); + } +} diff --git a/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button.tsx b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button.tsx new file mode 100644 index 000000000..7ae31c5d7 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-icon-label-button/nav-icon-label-button.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import fontSizeStyles from '../../styles/font-size.module.css'; +import gapStyles from '../../styles/gap.module.css'; +import paddingStyles from '../../styles/icon-label-button-padding.module.css'; +import iconSizeStyles from '../../styles/icon-size.module.css'; +import navFontSizeStyles from '../styles/nav-button-font-size.module.css'; +import navButtonIconSizeStyles from '../styles/nav-button-icon-size.module.css'; +import navGapStyles from './nav-button-gap.module.css'; +import navPaddingStyles from './nav-icon-label-button-padding.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { IconNode } from '../../types'; +import { NavBaseButtonProps } from '../types'; + +export type NavIconLabelButtonProps = { + children: [IconNode, string] | [string, IconNode] | [IconNode, string, IconNode]; +} & NavBaseButtonProps; + +export const NavIconLabelButton = forwardRef( + ({ size = 'medium', children, ...props }, ref) => ( + + {children} + + ), +); diff --git a/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button-padding.module.css b/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button-padding.module.css new file mode 100644 index 000000000..2c6c88aba --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button-padding.module.css @@ -0,0 +1,16 @@ +:root { + --ax-public-label-button-padding-xx-small: var(--ax-token-spacing-button-xxs-v-pad-1) + var(--ax-token-spacing-button-xxs-h-pad-1); + --ax-public-label-button-padding-xxx-small: var(--ax-token-spacing-button-xxxs-v-pad-1) + var(--ax-token-spacing-button-xxxs-h-pad-1); +} + +@layer ui.component { + .xx-small { + padding: var(--ax-public-label-button-padding-xx-small); + } + + .xxx-small { + padding: var(--ax-public-label-button-padding-xxx-small); + } +} diff --git a/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button.tsx b/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button.tsx new file mode 100644 index 000000000..2f837dab0 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/nav-label-button/nav-label-button.tsx @@ -0,0 +1,26 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import fontSizeStyles from '../../styles/font-size.module.css'; +import paddingStyles from '../../styles/label-button-padding.module.css'; +import navFontSizeStyles from '../styles/nav-button-font-size.module.css'; +import navPaddingStyles from './nav-label-button-padding.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { NavBaseButtonProps } from '../types'; + +export type NavLabelButtonProps = { + children: string; +} & NavBaseButtonProps; + +export const NavLabelButton = forwardRef( + ({ size = 'medium', children, ...props }, ref) => ( + + {children} + + ), +); diff --git a/packages/ui/src/components/button/nav-button/styles/nav-button-border-radius.module.css b/packages/ui/src/components/button/nav-button/styles/nav-button-border-radius.module.css new file mode 100644 index 000000000..36993ef80 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/styles/nav-button-border-radius.module.css @@ -0,0 +1,16 @@ +:root { + --ax-public-button-border-radius-xx-small: var(--ax-token-radius-button-xxs); + --ax-public-button-border-radius-xxx-small: var(--ax-token-radius-button-xxxs); +} + +@layer ui.component { + :not(.circle) { + &.xx-small { + border-radius: var(--ax-public-button-border-radius-xx-small); + } + + &.xxx-small { + border-radius: var(--ax-public-button-border-radius-xxx-small); + } + } +} diff --git a/packages/ui/src/components/button/nav-button/styles/nav-button-font-size.module.css b/packages/ui/src/components/button/nav-button/styles/nav-button-font-size.module.css new file mode 100644 index 000000000..e1eec5551 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/styles/nav-button-font-size.module.css @@ -0,0 +1,6 @@ +@layer ui.component { + .xx-small, + .xxx-small { + composes: ax-public-button-extra-small from global; + } +} diff --git a/packages/ui/src/components/button/nav-button/styles/nav-button-icon-size.module.css b/packages/ui/src/components/button/nav-button/styles/nav-button-icon-size.module.css new file mode 100644 index 000000000..14f15bb3e --- /dev/null +++ b/packages/ui/src/components/button/nav-button/styles/nav-button-icon-size.module.css @@ -0,0 +1,20 @@ +:root { + --ax-public-icon-size-xx-small: 0.75rem /* missing token */; + --ax-public-icon-size-xxx-small: 0.75rem /* missing token */; +} + +@layer ui.component { + .xx-small { + svg { + width: var(--ax-public-icon-size-xx-small); + height: var(--ax-public-icon-size-xx-small); + } + } + + .xxx-small { + svg { + width: var(--ax-public-icon-size-xxx-small); + height: var(--ax-public-icon-size-xxx-small); + } + } +} diff --git a/packages/ui/src/components/button/nav-button/styles/nav-button.module.css b/packages/ui/src/components/button/nav-button/styles/nav-button.module.css new file mode 100644 index 000000000..250c1a246 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/styles/nav-button.module.css @@ -0,0 +1,37 @@ +:root { + --ax-public-button-nav-background-color: var(--ax-nav-button-bg-primary-hover); + --ax-public-button-nav-color: var(--ax-nav-button-icon-primary-default); + --ax-public-button-nav-color-active: var(--ax-nav-button-icon-primary-pressed); + --ax-public-button-nav-color-disabled: var(--ax-nav-button-icon-primary-disabled); +} + +@layer ui.component { + .nav-button { + background-color: transparent; + color: var(--ax-public-button-nav-color); + + &:hover { + background-color: var(--ax-public-button-nav-background-color); + } + + &:active { + color: var(--ax-public-button-nav-color-active); + background-color: var(--ax-public-button-nav-background-color); + } + + &:focus-visible { + background-color: var(--ax-public-button-nav-background-color); + } + + &:disabled { + color: var(--ax-public-button-nav-color-disabled); + background-color: transparent; + } + + &.selected { + pointer-events: none; + background-color: var(--ax-button-primary-bg-default); + color: var(--ax-nav-button-icon-primary-active); + } + } +} diff --git a/packages/ui/src/components/button/nav-button/types.ts b/packages/ui/src/components/button/nav-button/types.ts new file mode 100644 index 000000000..7394cbe98 --- /dev/null +++ b/packages/ui/src/components/button/nav-button/types.ts @@ -0,0 +1,8 @@ +import { Size } from '@ui/shared/types/size'; + +import { BaseButtonProps } from '../types'; + +export type NavBaseButtonProps = BaseButtonProps & { + size?: Size; + isSelected?: boolean; +}; diff --git a/packages/ui/src/components/button/regular-button/button.tsx b/packages/ui/src/components/button/regular-button/button.tsx new file mode 100644 index 000000000..1667083d8 --- /dev/null +++ b/packages/ui/src/components/button/regular-button/button.tsx @@ -0,0 +1,108 @@ +import clsx from 'clsx'; +import { ReactElement, forwardRef } from 'react'; + +import borderRadiusStyles from '../styles/border-radius.module.css'; +import variantStyles from '../styles/variant.module.css'; + +import { hasChildrenWithStringAndIcons, hasIconChildrenOnly, hasStringChildrenOnly } from '../guards'; +import { IconButton, IconButtonProps } from './icon-button/icon-button'; +import { IconLabelButton, IconLabelButtonProps } from './icon-label-button/icon-label-button'; +import { LabelButton, LabelButtonProps } from './label-button/label-button'; + +type WithRef = T & { + ref?: React.Ref; +}; + +/** + * ButtonProps defines **discriminated overloads** for the Button component using + * **structural discrimination** rather than a `type` field. + * + * The component dynamically determines which button variant to render based on the + * **structure of the `children` prop**: + * + * - If `children` is a single `string`, it's treated as a **Label Button**. + * - If `children` is a single icon (ReactElement), it's treated as an **Icon Button**. + * - If `children` includes both a string and one or two icons (before/after), + * it's treated as an **Icon Label Button**. + * + * Based on the inferred variant, **only props specific to that variant are allowed**. + * This ensures that incorrect prop combinations (e.g., passing label-specific props + * to an Icon Button) are caught at compile time. + * + * This is intentionally implemented with **overloads** instead of a union type, + * which would incorrectly allow mixing props between types and compromise type safety. + */ +type ButtonProps = { + (props: WithRef): ReactElement; + (props: WithRef): ReactElement; + (props: WithRef): ReactElement; +}; + +const ButtonComponent = forwardRef( + ({ className, variant = 'primary', size = 'medium', ...props }, ref) => { + const buttonProps = { + ref, + ...props, + className: clsx(variantStyles[variant], borderRadiusStyles[size], className), + variant, + size, + }; + + if (hasStringChildrenOnly(props)) { + return {props.children}; + } + + if (hasIconChildrenOnly(props)) { + return {props.children}; + } + + if (hasChildrenWithStringAndIcons(props)) { + return {props.children}; + } + + // Unreachable for type-checked callers; guards dynamic children TS cannot see. + console.error( + 'Button: `children` did not match a supported variant (label, icon, or icon + label). Rendering nothing.', + ); + return null; + }, +); + +/** + * Button is a flexible, and type-safe component that automatically selects + * the correct type (Label Button, Icon Button, or Icon Label Button) based on the + * structure of its `children` prop. + * + * **Automatic Type Selection (Structural Discrimination)** + * The component uses the shape of `children` to infer which button variant to render: + * - **Label Button**: If `children` is a single `string` + * - **Icon Button**: If `children` is a single React element (e.g., an icon) + * - **Icon Label Button**: If `children` is a combination of string + icon(s) + * + * **Type Safety via Overloads** + * Each variant supports its own unique set of props. Thanks to TypeScript overloads, + * only the correct props for a given structure are allowed—invalid combinations + * are caught at compile time. + * + * **How to Use** + * + * ```tsx + * // Label Button + * + * // Icon Button + * + * // Icon Label Button + * ``` + * + * This approach ensures: + * - Simplified usage with fewer props + * - No accidental mixing of incompatible props + * - Autocomplete and type-checking experience + */ +export const Button = ButtonComponent as ButtonProps; diff --git a/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx b/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx new file mode 100644 index 000000000..55402c84e --- /dev/null +++ b/packages/ui/src/components/button/regular-button/icon-button/icon-button.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import borderRadiusStyles from '../../styles/border-radius.module.css'; +import iconPaddingStyles from '../../styles/icon-padding.module.css'; +import iconSizeStyles from '../../styles/icon-size.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { IconNode, Shape } from '../../types'; +import { BaseRegularButtonProps } from '../types'; + +export type IconButtonProps = { + shape?: Shape; + children: IconNode; +} & BaseRegularButtonProps; + +export const IconButton = forwardRef( + ({ size = 'medium', shape = 'default', children, ...props }, ref) => ( + + {children} + + ), +); diff --git a/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx b/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx new file mode 100644 index 000000000..ec53f6a4e --- /dev/null +++ b/packages/ui/src/components/button/regular-button/icon-label-button/icon-label-button.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import fontSizeStyles from '../../styles/font-size.module.css'; +import gapStyles from '../../styles/gap.module.css'; +import paddingStyles from '../../styles/icon-label-button-padding.module.css'; +import iconSizeStyles from '../../styles/icon-size.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { IconNode } from '../../types'; +import { BaseRegularButtonProps } from '../types'; + +export type IconLabelButtonProps = { + children: [IconNode, string] | [string, IconNode] | [IconNode, string, IconNode]; +} & BaseRegularButtonProps; + +export const IconLabelButton = forwardRef( + ({ size = 'medium', children, ...props }, ref) => ( + + {children} + + ), +); diff --git a/packages/ui/src/components/button/regular-button/label-button/label-button.tsx b/packages/ui/src/components/button/regular-button/label-button/label-button.tsx new file mode 100644 index 000000000..ab0041b7b --- /dev/null +++ b/packages/ui/src/components/button/regular-button/label-button/label-button.tsx @@ -0,0 +1,29 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import fontSizeStyles from '../../styles/font-size.module.css'; +import paddingStyles from '../../styles/label-button-padding.module.css'; +import loaderStyles from './loader.module.css'; + +import { BaseButton } from '../../base-button/base-button'; +import { BaseRegularButtonProps } from '../types'; + +export type LabelButtonProps = { + isLoading?: boolean; + children: string; +} & BaseRegularButtonProps; + +export const LabelButton = forwardRef( + ({ size = 'medium', isLoading, children, ...props }, ref) => ( + + {{children}} + {isLoading && } + + ), +); diff --git a/packages/ui/src/components/button/regular-button/label-button/loader.module.css b/packages/ui/src/components/button/regular-button/label-button/loader.module.css new file mode 100644 index 000000000..904870f8f --- /dev/null +++ b/packages/ui/src/components/button/regular-button/label-button/loader.module.css @@ -0,0 +1,58 @@ +:root { + --ax-public-button-loader-dot-size: 0.3125rem; + --ax-public-button-loader-dot-border-radius: 0.3125rem; + --ax-public-button-loader-dot-color: var(--ax-txt-primary-white); + --ax-public-button-loader-animation-duration: 0.75s; +} + +@layer ui.component { + .disable-events { + pointer-events: none; + } + + .hide-label { + opacity: 0; + } + + .dot-flashing, + .dot-flashing::before, + .dot-flashing::after { + position: absolute; + width: var(--ax-public-button-loader-dot-size); + height: var(--ax-public-button-loader-dot-size); + border-radius: var(--ax-public-button-loader-dot-border-radius); + background-color: var(--ax-public-button-loader-dot-color); + color: var(--ax-public-button-loader-dot-color); + animation: dot-flashing var(--ax-public-button-loader-animation-duration) infinite linear alternate; + } + + .dot-flashing { + animation-delay: calc(var(--ax-public-button-loader-animation-duration) / 2); + } + + .dot-flashing::before, + .dot-flashing::after { + content: ''; + display: inline-block; + top: 0; + } + + .dot-flashing::before { + left: calc(-1 * var(--ax-public-button-loader-dot-size) * 2); + animation-delay: 0s; + } + + .dot-flashing::after { + left: calc(var(--ax-public-button-loader-dot-size) * 2); + animation-delay: var(--ax-public-button-loader-animation-duration); + } + + @keyframes dot-flashing { + 0% { + opacity: 1; + } + 100% { + opacity: 0.2; + } + } +} diff --git a/packages/ui/src/components/button/regular-button/types.ts b/packages/ui/src/components/button/regular-button/types.ts new file mode 100644 index 000000000..c18dc4bdf --- /dev/null +++ b/packages/ui/src/components/button/regular-button/types.ts @@ -0,0 +1,24 @@ +import { SIZES, Size } from '../../../shared/types/size'; +import { rangeBetween } from '../../../shared/utils/arrays'; +import { BaseButtonProps } from '../types'; + +export const BUTTON_VARIANTS = [ + 'primary', + 'secondary', + 'gray', + 'error', + 'warning', + 'success', + 'ghost-destructive', +] as const; + +export const BUTTON_SIZES = rangeBetween(SIZES, 'extra-small', 'extra-large'); + +export type ButtonSize = Extract; + +export type Variant = (typeof BUTTON_VARIANTS)[number]; + +export type BaseRegularButtonProps = BaseButtonProps & { + variant?: Variant; + size?: ButtonSize; +}; diff --git a/packages/ui/src/components/button/styles/border-radius.module.css b/packages/ui/src/components/button/styles/border-radius.module.css new file mode 100644 index 000000000..00c52cc37 --- /dev/null +++ b/packages/ui/src/components/button/styles/border-radius.module.css @@ -0,0 +1,37 @@ +:root { + --ax-public-button-border-radius-extra-large: var(--ax-token-radius-button-xl); + --ax-public-button-border-radius-large: var(--ax-token-radius-button-l); + --ax-public-button-border-radius-medium: var(--ax-token-radius-button-m); + --ax-public-button-border-radius-small: var(--ax-token-radius-button-s); + --ax-public-button-border-radius-extra-small: var(--ax-token-radius-button-xs); + + --ax-public-button-border-radius-circle: var(--ax-token-radius-button-round); +} + +@layer ui.component { + :not(.circle) { + &.extra-large { + border-radius: var(--ax-public-button-border-radius-extra-large); + } + + &.large { + border-radius: var(--ax-public-button-border-radius-large); + } + + &.medium { + border-radius: var(--ax-public-button-border-radius-medium); + } + + &.small { + border-radius: var(--ax-public-button-border-radius-small); + } + + &.extra-small { + border-radius: var(--ax-public-button-border-radius-extra-small); + } + } + + .circle { + border-radius: var(--ax-public-button-border-radius-circle); + } +} diff --git a/packages/ui/src/components/button/styles/font-size.module.css b/packages/ui/src/components/button/styles/font-size.module.css new file mode 100644 index 000000000..e65ebb472 --- /dev/null +++ b/packages/ui/src/components/button/styles/font-size.module.css @@ -0,0 +1,15 @@ +@layer ui.component { + .extra-large { + composes: ax-public-button-large from global; + } + + .large { + composes: ax-public-button-medium from global; + } + + .medium, + .small, + .extra-small { + composes: ax-public-button-small from global; + } +} diff --git a/packages/ui/src/components/button/styles/gap.module.css b/packages/ui/src/components/button/styles/gap.module.css new file mode 100644 index 000000000..574c51ab9 --- /dev/null +++ b/packages/ui/src/components/button/styles/gap.module.css @@ -0,0 +1,29 @@ +:root { + --ax-public-button-gap-extra-large: var(--ax-token-spacing-button-xl-gap); + --ax-public-button-gap-large: var(--ax-token-spacing-button-l-gap); + --ax-public-button-gap-medium: var(--ax-token-spacing-button-m-gap); + --ax-public-button-gap-small: var(--ax-token-spacing-button-s-gap); + --ax-public-button-gap-extra-small: var(--ax-token-spacing-button-xs-gap); +} + +@layer ui.component { + .extra-large { + gap: var(--ax-public-button-gap-extra-large); + } + + .large { + gap: var(--ax-public-button-gap-large); + } + + .medium { + gap: var(--ax-public-button-gap-medium); + } + + .small { + gap: var(--ax-public-button-gap-small); + } + + .extra-small { + gap: var(--ax-public-button-gap-extra-small); + } +} diff --git a/packages/ui/src/components/button/styles/icon-label-button-padding.module.css b/packages/ui/src/components/button/styles/icon-label-button-padding.module.css new file mode 100644 index 000000000..4684f5a19 --- /dev/null +++ b/packages/ui/src/components/button/styles/icon-label-button-padding.module.css @@ -0,0 +1,34 @@ +:root { + --ax-public-icon-label-button-padding-extra-large: var(--ax-token-spacing-button-xl-v-pad-2) + var(--ax-token-spacing-button-xl-h-pad-2); + --ax-public-icon-label-button-padding-large: var(--ax-token-spacing-button-l-v-pad-2) + var(--ax-token-spacing-button-l-h-pad-2); + --ax-public-icon-label-button-padding-medium: var(--ax-token-spacing-button-m-v-pad-2) + var(--ax-token-spacing-button-m-h-pad-2); + --ax-public-icon-label-button-padding-small: var(--ax-token-spacing-button-s-v-pad-2) + var(--ax-token-spacing-button-s-h-pad-2); + --ax-public-icon-label-button-padding-extra-small: var(--ax-token-spacing-button-xs-v-pad-2) + var(--ax-token-spacing-button-xs-h-pad-2); +} + +@layer ui.component { + .extra-large { + padding: var(--ax-public-icon-label-button-padding-extra-large); + } + + .large { + padding: var(--ax-public-icon-label-button-padding-large); + } + + .medium { + padding: var(--ax-public-icon-label-button-padding-medium); + } + + .small { + padding: var(--ax-public-icon-label-button-padding-small); + } + + .extra-small { + padding: var(--ax-public-icon-label-button-padding-extra-small); + } +} diff --git a/packages/ui/src/components/button/styles/icon-padding.module.css b/packages/ui/src/components/button/styles/icon-padding.module.css new file mode 100644 index 000000000..82d9fdb0b --- /dev/null +++ b/packages/ui/src/components/button/styles/icon-padding.module.css @@ -0,0 +1,29 @@ +:root { + --ax-public-button-icon-padding-extra-large: var(--ax-token-spacing-button-xl-v-pad-2); + --ax-public-button-icon-padding-large: var(--ax-token-spacing-button-l-v-pad-2); + --ax-public-button-icon-padding-medium: var(--ax-token-spacing-button-m-v-pad-2); + --ax-public-button-icon-padding-small: var(--ax-token-spacing-button-s-v-pad-2); + --ax-public-button-icon-padding-extra-small: var(--ax-token-spacing-button-xs-v-pad-2); +} + +@layer ui.component { + .extra-large { + padding: var(--ax-public-button-icon-padding-extra-large); + } + + .large { + padding: var(--ax-public-button-icon-padding-large); + } + + .medium { + padding: var(--ax-public-button-icon-padding-medium); + } + + .small { + padding: var(--ax-public-button-icon-padding-small); + } + + .extra-small { + padding: var(--ax-public-button-icon-padding-extra-small); + } +} diff --git a/packages/ui/src/components/button/styles/icon-size.module.css b/packages/ui/src/components/button/styles/icon-size.module.css new file mode 100644 index 000000000..e965778ff --- /dev/null +++ b/packages/ui/src/components/button/styles/icon-size.module.css @@ -0,0 +1,44 @@ +:root { + --ax-public-icon-size-extra-large: 1.5rem /* missing token */; + --ax-public-icon-size-large: 1.25rem /* missing token */; + --ax-public-icon-size-medium: 1.125rem /* missing token */; + --ax-public-icon-size-small: 1.125rem /* missing token */; + --ax-public-icon-size-extra-small: 1rem /* missing token */; +} + +@layer ui.component { + .extra-large { + svg { + width: var(--ax-public-icon-size-extra-large); + height: var(--ax-public-icon-size-extra-large); + } + } + + .large { + svg { + width: var(--ax-public-icon-size-large); + height: var(--ax-public-icon-size-large); + } + } + + .medium { + svg { + width: var(--ax-public-icon-size-medium); + height: var(--ax-public-icon-size-medium); + } + } + + .small { + svg { + width: var(--ax-public-icon-size-small); + height: var(--ax-public-icon-size-small); + } + } + + .extra-small { + svg { + width: var(--ax-public-icon-size-extra-small); + height: var(--ax-public-icon-size-extra-small); + } + } +} diff --git a/packages/ui/src/components/button/styles/label-button-padding.module.css b/packages/ui/src/components/button/styles/label-button-padding.module.css new file mode 100644 index 000000000..100c54681 --- /dev/null +++ b/packages/ui/src/components/button/styles/label-button-padding.module.css @@ -0,0 +1,31 @@ +:root { + --ax-public-label-button-padding-extra-large: var(--ax-token-spacing-button-xl-v-pad-1) + var(--ax-token-spacing-button-xl-h-pad-1); + --ax-public-label-button-padding-large: var(--ax-token-spacing-button-l-v-pad-1) + var(--ax-token-spacing-button-l-h-pad-1); + --ax-public-label-button-padding-medium: var(--ax-token-spacing-button-m-v-pad-1) + var(--ax-token-spacing-button-m-h-pad-1); + --ax-public-label-button-padding-small: var(--ax-token-spacing-button-s-v-pad-1) + var(--ax-token-spacing-button-s-h-pad-1); + --ax-public-label-button-padding-extra-small: var(--ax-token-spacing-button-xs-v-pad-1) + var(--ax-token-spacing-button-xs-h-pad-1); +} + +@layer ui.component { + .extra-large, + .large { + padding: var(--ax-public-label-button-padding-extra-large); + } + + .medium { + padding: var(--ax-public-label-button-padding-medium); + } + + .small { + padding: var(--ax-public-label-button-padding-small); + } + + .extra-small { + padding: var(--ax-public-label-button-padding-extra-small); + } +} diff --git a/packages/ui/src/components/button/styles/variant.module.css b/packages/ui/src/components/button/styles/variant.module.css new file mode 100644 index 000000000..4637f465e --- /dev/null +++ b/packages/ui/src/components/button/styles/variant.module.css @@ -0,0 +1,180 @@ +:root { + --ax-public-button-border-size: 0.0625rem; + + --ax-public-button-primary-background: var(--ax-button-primary-bg-default); + --ax-public-button-primary-background-hover: var(--ax-button-primary-bg-hover); + --ax-public-button-primary-background-active: var(--ax-button-primary-bg-active); + --ax-public-button-primary-background-focus: var(--ax-button-primary-bg-focus); + + --ax-public-button-secondary-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-button-secondary-color: var(--ax-txt-primary-default); + --ax-public-button-secondary-border-color-hover: var(--ax-ui-stroke-primary-focus); + --ax-public-button-secondary-color-active: var(--ax-txt-secondary-default); + --ax-public-button-secondary-border-color-active: var(--ax-ui-stroke-primary-focus); + --ax-public-button-secondary-border-color-focus: var(--ax-ui-stroke-primary-focus); + --ax-public-button-secondary-border-color-disabled: var(--ax-ui-stroke-secondary-default); + --ax-public-button-secondary-color-disabled: var(--ax-txt-quaternary-default); + + --ax-public-button-ghost-destructive-background-color: var(--ax-button-ghost-destructive-bg-default); + --ax-public-button-ghost-destructive-border-color: var(--ax-button-ghost-destructive-stroke-default); + --ax-public-button-ghost-destructive-color: var(--ax-txt-destuctive-default); + --ax-public-button-ghost-destructive-background-color-hover: var(--ax-button-ghost-destructive-bg-hover); + --ax-public-button-ghost-destructive-background-color-active: var(--ax-button-ghost-destructive-bg-active); + --ax-public-button-ghost-destructive-background-color-focus: var(--ax-button-ghost-destructive-bg-focus); + --ax-public-button-ghost-destructive-background-color-disabled: var(--ax-button-ghost-destructive-bg-disabled); + --ax-public-button-ghost-destructive-color-disabled: var(--ax-txt-destructive-disabled); + + --ax-public-button-gray-background: var(--ax-button-gray-bg-default); + --ax-public-button-gray-background-hover: var(--ax-button-gray-bg-hover); + --ax-public-button-gray-background-active: var(--ax-button-gray-bg-active); + --ax-public-button-gray-background-focus: var(--ax-button-gray-bg-focus); + + --ax-public-button-error-background: var(--ax-button-red-bg-default); + --ax-public-button-error-background-hover: var(--ax-button-red-bg-hover); + --ax-public-button-error-background-active: var(--ax-button-red-bg-active); + --ax-public-button-error-background-focus: var(--ax-button-red-bg-focus); + + --ax-public-button-success-background: var(--ax-button-green-bg-default); + --ax-public-button-success-background-hover: var(--ax-button-green-bg-hover); + --ax-public-button-success-background-active: var(--ax-button-green-bg-active); + --ax-public-button-success-background-focus: var(--ax-button-green-bg-focus); + + --ax-public-button-warning-background: var(--ax-button-orange-bg-default); + --ax-public-button-warning-background-hover: var(--ax-button-orange-bg-hover); + --ax-public-button-warning-background-active: var(--ax-button-orange-bg-active); + --ax-public-button-warning-background-focus: var(--ax-button-orange-bg-focus); +} + +@layer ui.component { + button { + &:not(:disabled) { + &.primary { + background-color: var(--ax-public-button-primary-background); + + &:hover { + background-color: var(--ax-public-button-primary-background-hover); + } + + &:active { + background-color: var(--ax-public-button-primary-background-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-primary-background-focus); + } + } + + &.secondary { + border: var(--ax-public-button-border-size) solid var(--ax-public-button-secondary-border-color); + background-color: transparent; + color: var(--ax-public-button-secondary-color); + + &:hover { + border-color: var(--ax-public-button-secondary-border-color-hover); + } + + &:active { + border-color: var(--ax-public-button-secondary-border-color-active); + color: var(--ax-public-button-secondary-color-active); + } + + &:focus-visible { + border-color: var(--ax-public-button-secondary-border-color-focus); + } + + &:disabled { + border-color: var(--ax-public-button-secondary-border-color-disabled); + color: var(--ax-public-button-secondary-color-disabled); + } + } + + &.ghost-destructive { + border: var(--ax-public-button-border-size) solid var(--ax-public-button-ghost-destructive-border-color); + background-color: var(--ax-public-button-ghost-destructive-background-color); + color: var(--ax-public-button-ghost-destructive-color); + + &:hover { + background-color: var(--ax-public-button-ghost-destructive-background-color-hover); + } + + &:active { + background-color: var(--ax-public-button-ghost-destructive-background-color-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-ghost-destructive-background-color-focus); + border-width: 2px; + } + + &:disabled { + background-color: var(--ax-public-button-ghost-destructive-background-color-disabled); + color: var(--ax-public-button-ghost-destructive-color-disabled); + } + } + + &.gray { + background-color: var(--ax-public-button-gray-background); + + &:hover { + background-color: var(--ax-public-button-gray-background-hover); + } + + &:active { + background-color: var(--ax-public-button-gray-background-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-gray-background-focus); + } + } + + &.error { + background-color: var(--ax-public-button-error-background); + + &:hover { + background-color: var(--ax-public-button-error-background-hover); + } + + &:active { + background-color: var(--ax-public-button-error-background-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-error-background-focus); + } + } + + &.success { + background-color: var(--ax-public-button-success-background); + + &:hover { + background-color: var(--ax-public-button-success-background-hover); + } + + &:active { + background-color: var(--ax-public-button-success-background-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-success-background-focus); + } + } + + &.warning { + background-color: var(--ax-public-button-warning-background); + + &:hover { + background-color: var(--ax-public-button-warning-background-hover); + } + + &:active { + background-color: var(--ax-public-button-warning-background-active); + } + + &:focus-visible { + background-color: var(--ax-public-button-warning-background-focus); + } + } + } + } +} diff --git a/packages/ui/src/components/button/types.ts b/packages/ui/src/components/button/types.ts new file mode 100644 index 000000000..fda418b92 --- /dev/null +++ b/packages/ui/src/components/button/types.ts @@ -0,0 +1,10 @@ +import { TooltipVariant } from '../tooltip/types'; + +export type Shape = 'default' | 'circle'; + +export type IconNode = React.ReactElement; + +export type BaseButtonProps = { + tooltip?: string; + tooltipType?: TooltipVariant; +} & React.DetailedHTMLProps, HTMLButtonElement>; diff --git a/packages/ui/src/components/checkbox/checkbox.module.css b/packages/ui/src/components/checkbox/checkbox.module.css new file mode 100644 index 000000000..eec763654 --- /dev/null +++ b/packages/ui/src/components/checkbox/checkbox.module.css @@ -0,0 +1,130 @@ +:root { + --ax-public-checkbox-size-medium: 1.25rem; + --ax-public-checkbox-size-small: 1.125rem; + --ax-public-checkbox-size-extra-small: 1rem; + + --ax-public-checkbox-icon-size-medium: 0.875rem; + --ax-public-checkbox-icon-size-small: 0.75rem; + --ax-public-checkbox-icon-size-extra-small: 0.625rem; + + --ax-public-checkbox-border-radius-medium: var(--ax-token-radius-checkbox-m); + --ax-public-checkbox-border-radius-small: var(--ax-token-radius-checkbox-s); + --ax-public-checkbox-border-radius-extra-small: var(--ax-token-radius-checkbox-xs); + + --ax-public-checkbox-border-width: 0.0625rem; + + --ax-public-checkbox-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-checkbox-bg: var(--ax-ui-bg-primary-default); + --ax-public-checkbox-icon-color: var(--ax-colors-gray-100); /* missing token */ + + --ax-public-checkbox-checked-bg: var(--ax-ui-bg-tertiary-selected); + + --ax-public-checkbox-focus-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-checkbox-focus-border-shadow: var(--ax-txt-primary-default); + + --ax-public-checkbox-disabled-bg: var(--ax-ui-bg-tertiary-default); + --ax-public-checkbox-disabled-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-checkbox-disabled-icon-color: var(--ax-nav-button-icon-primary-disabled); +} + +@layer ui.component { + .container { + position: relative; + display: inline-flex; + cursor: pointer; + } + + .icon { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: none; + color: var(--ax-public-checkbox-icon-color); + line-height: 0; + + &.medium { + svg { + width: var(--ax-public-checkbox-icon-size-medium); + height: var(--ax-public-checkbox-icon-size-medium); + } + } + + &.small { + svg { + width: var(--ax-public-checkbox-icon-size-small); + height: var(--ax-public-checkbox-icon-size-small); + } + } + + &.extra-small { + svg { + width: var(--ax-public-checkbox-icon-size-extra-small); + height: var(--ax-public-checkbox-icon-size-extra-small); + } + } + } + + .checkbox { + appearance: none; + display: inline-block; + position: relative; + margin: 0; + border: var(--ax-public-checkbox-border-width) solid var(--ax-public-checkbox-border-color); + background-color: white; + cursor: pointer; + vertical-align: middle; + transition: all var(--ax-public-transition); + + &:checked, + &:indeterminate { + background-color: var(--ax-public-checkbox-checked-bg); + border-color: transparent; + } + + &:checked ~ .icon, + &:indeterminate ~ .icon { + display: block; + } + + &:disabled:checked, + &:disabled:indeterminate { + background-color: var(--ax-public-checkbox-disabled-bg); + border-color: var(--ax-public-checkbox-disabled-border-color); + } + + &:disabled ~ .icon { + color: var(--ax-public-checkbox-disabled-icon-color); + } + + &:disabled { + cursor: auto; + background-color: var(--ax-public-checkbox-disabled-bg); + border-color: var(--ax-public-checkbox-disabled-border-color); + } + + &:focus-visible { + box-shadow: 0px 0px 0px 2px var(--ax-public-checkbox-focus-border-shadow); + border-color: var(--ax-public-checkbox-border-color); + outline: none; + } + + &.medium { + width: var(--ax-public-checkbox-size-medium); + height: var(--ax-public-checkbox-size-medium); + border-radius: var(--ax-public-checkbox-border-radius-medium); + } + + &.small { + width: var(--ax-public-checkbox-size-small); + height: var(--ax-public-checkbox-size-small); + border-radius: var(--ax-public-checkbox-border-radius-small); + } + + &.extra-small { + width: var(--ax-public-checkbox-size-extra-small); + height: var(--ax-public-checkbox-size-extra-small); + border-radius: var(--ax-public-checkbox-border-radius-extra-small); + } + } +} diff --git a/packages/ui/src/components/checkbox/checkbox.tsx b/packages/ui/src/components/checkbox/checkbox.tsx new file mode 100644 index 000000000..44b64798e --- /dev/null +++ b/packages/ui/src/components/checkbox/checkbox.tsx @@ -0,0 +1,50 @@ +import { Check, Minus } from '@phosphor-icons/react'; +import type { SelectorSize } from '@ui/shared/types/selector-size'; +import clsx from 'clsx'; +import type { InputHTMLAttributes } from 'react'; + +import styles from './checkbox.module.css'; + +type Props = { + /** + * The size of the checkbox + */ + size?: SelectorSize; + /** + * Whether the checkbox is in an indeterminate state + */ + indeterminate?: boolean; + /** + * Whether the checkbox is checked + */ + checked?: boolean; +} & Omit, 'size'>; + +/** + * A customizable checkbox component that supports three states: checked, unchecked, and indeterminate. It can be used in forms or as a standalone control. + */ +export function Checkbox({ size = 'medium', className, indeterminate, checked, onChange, ...props }: Props) { + function handleChange(event: React.ChangeEvent) { + onChange?.(event); + } + + return ( + + ); +} diff --git a/packages/ui/src/components/checkbox/index.ts b/packages/ui/src/components/checkbox/index.ts new file mode 100644 index 000000000..8d78b3e23 --- /dev/null +++ b/packages/ui/src/components/checkbox/index.ts @@ -0,0 +1 @@ +export * from './checkbox'; diff --git a/packages/ui/src/components/collapsible/collapsible.module.css b/packages/ui/src/components/collapsible/collapsible.module.css new file mode 100644 index 000000000..6bc3d9e5a --- /dev/null +++ b/packages/ui/src/components/collapsible/collapsible.module.css @@ -0,0 +1,35 @@ +:root { + --ax-public-collapsible-transition-duration: 0.2s; +} + +@layer ui.component { + .expand-button { + z-index: 10; + + svg { + transition: transform var(--ax-public-collapsible-transition-duration) ease; + } + + &.expanded { + svg { + transform: rotate(180deg); + } + } + } + + .content-wrapper { + display: grid; + grid-template-rows: 0fr; + overflow: hidden; + transition: grid-template-rows var(--ax-public-collapsible-transition-duration) ease; + + &.expanded { + overflow: unset; + grid-template-rows: 1fr; + } + + .content { + min-height: 0; + } + } +} diff --git a/packages/ui/src/components/collapsible/collapsible.tsx b/packages/ui/src/components/collapsible/collapsible.tsx new file mode 100644 index 000000000..e2fbc59bd --- /dev/null +++ b/packages/ui/src/components/collapsible/collapsible.tsx @@ -0,0 +1,101 @@ +import { CaretUp } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import { ReactNode, RefObject, createContext, useCallback, useContext, useRef, useState } from 'react'; + +import styles from './collapsible.module.css'; + +import { useTransitionEvent } from '../../shared/hooks/use-transition-event'; +import { NavButton } from '../button/nav-button/nav-button'; + +interface CollapsibleContextProps { + isExpanded: boolean; + toggle: () => void; +} + +const CollapsibleContext = createContext(undefined); + +function useCollapsibleContext() { + const context = useContext(CollapsibleContext); + if (context) { + return context; + } + + console.error(' and must be used within '); +} + +export type CollapsibleProps = { + children: ReactNode; + isExpanded?: boolean; + defaultExpanded?: boolean; + onToggle?: (expanded: boolean) => void; +}; + +export function Collapsible({ children, isExpanded, defaultExpanded = false, onToggle }: CollapsibleProps) { + const isControlled = isExpanded !== undefined; + const [internalExpanded, setInternalExpanded] = useState(defaultExpanded); + + const actualExpanded = isControlled ? isExpanded : internalExpanded; + + const toggle = useCallback(() => { + const next = !actualExpanded; + if (!isControlled) { + setInternalExpanded(next); + } + onToggle?.(next); + }, [actualExpanded, isControlled, onToggle]); + + return ( + {children} + ); +} + +Collapsible.Button = function CollapsibleButton() { + const context = useCollapsibleContext(); + + return ( + + + + ); +}; + +Collapsible.Content = function CollapsibleContent({ children }: { children: ReactNode }) { + const ref = useRef(null); + const context = useCollapsibleContext(); + + useOverflowDuringTransition(ref, context?.isExpanded || false); + + return ( +
+
{children}
+
+ ); +}; + +function useOverflowDuringTransition( + ref: RefObject, + isExpanded: boolean, + transitionProperty: string = 'grid-template-rows', +) { + useTransitionEvent(ref, 'transitionstart', transitionProperty, () => { + if (!isExpanded && ref.current) { + ref.current.style.overflow = 'hidden'; + } + }); + + useTransitionEvent(ref, 'transitionend', transitionProperty, () => { + if (isExpanded && ref.current) { + ref.current.style.overflow = 'unset'; + } + }); +} diff --git a/packages/ui/src/components/collapsible/index.ts b/packages/ui/src/components/collapsible/index.ts new file mode 100644 index 000000000..20c0813c0 --- /dev/null +++ b/packages/ui/src/components/collapsible/index.ts @@ -0,0 +1 @@ +export * from './collapsible'; diff --git a/packages/ui/src/components/date-picker/date-picker.module.css b/packages/ui/src/components/date-picker/date-picker.module.css new file mode 100644 index 000000000..701b66a61 --- /dev/null +++ b/packages/ui/src/components/date-picker/date-picker.module.css @@ -0,0 +1,72 @@ +@layer ui.component { + .container { + background: transparent; + border: var(--ax-public-input-root-border-size) solid var(--ax-public-input-root-border-color); + color: var(--ax-public-input-root-color); + cursor: pointer; + width: 100%; + + &[aria-expanded='true'] { + border-color: var(--ax-public-input-root-border-color-focus); + } + + &.container--error { + background-color: var(--ax-public-input-root-background-color-error); + border-color: var(--ax-public-input-root-border-color-error); + color: var(--ax-public-input-root-color-error); + } + + &.container--placeholder { + opacity: 0.5; + } + } + + .trigger-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .calendar { + background: var(--ax-public-date-picker-dropdown-background); + border: 0.0625rem solid var(--ax-public-date-picker-dropdown-border-color); + border-radius: 0.5rem; + box-shadow: var(--ax-public-date-picker-dropdown-box-shadow); + color: var(--ax-public-date-picker-color); + } + + /* + * react-day-picker theming. The library's own style.css is imported into + * `ui.base` (see variables.css), so these `ui.component` rules win by layer + * order alone - load order and specificity don't matter. + * `.calendar` is the Popover popup that wraps the calendar. + */ + .calendar :global(.rdp-root) { + --rdp-day_button-border-radius: 0.375rem; + --rdp-selected-border: none; + --rdp-today-color: var(--ax-public-date-picker-date-today-color); + /* retheme react-day-picker's accent (range + endpoints, focus rings) to our token instead. */ + --rdp-accent-color: var(--ax-public-date-picker-date-today-color); + } + + .calendar :global(.rdp-day_button) { + transition: background-color 0.12s ease; + } + + /* month-nav chevrons: react-day-picker paints them with --rdp-accent-color; + render them as a neutral nav control so both arrows match each other and the + rest of the calendar. */ + .calendar :global(.rdp-chevron) { + fill: var(--ax-public-date-picker-nav-color); + } + + .calendar :global(.rdp-day:not(.rdp-disabled):not(.rdp-selected) .rdp-day_button:hover) { + background-color: var(--ax-public-date-picker-date-hover-background-color); + } + + .calendar :global(.rdp-selected .rdp-day_button) { + background-color: var(--ax-public-date-picker-date-selected-background-color); + color: var(--ax-public-date-picker-date-selected-color); + } +} diff --git a/packages/ui/src/components/date-picker/date-picker.tsx b/packages/ui/src/components/date-picker/date-picker.tsx new file mode 100644 index 000000000..9fa4c07bc --- /dev/null +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -0,0 +1,290 @@ +import { Popover } from '@base-ui/react/popover'; +import { CaretLeft, CaretRight } from '@phosphor-icons/react'; +import clsx from 'clsx'; +import { format } from 'date-fns'; +import { forwardRef, useCallback, useMemo, useState } from 'react'; +import { type DateRange, DayPicker, type Matcher } from 'react-day-picker'; + +import styles from './date-picker.module.css'; +// variables.css also pulls react-day-picker's stylesheet into `ui.base`. +import './variables.css'; +import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; +import inputSizeStyles from '@ui/shared/styles/input-size.module.css'; +import listBoxStyles from '@ui/shared/styles/list-box.module.css'; + +import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; +import type { DatePickerProps, DatePickerType } from './types'; + +type Props = DatePickerProps & { + /** + * Format string used to render the selected date(s) in the trigger. + * + * **Note:** This implementation uses `date-fns` format tokens (e.g. + * `dd/MM/yyyy`). The legacy default value `DD/MM/YYYY` (dayjs tokens) is + * accepted and converted to the equivalent `date-fns` tokens for backwards + * compatibility. + * + * @default 'DD/MM/YYYY' + */ + valueFormat?: string; + /** + * Placeholder text shown when no date is selected. + * + * @default 'dd/mm/yyyy' + */ + placeholder?: string; + /** + * Picker type. + * + * - `default` selects a single date + * - `range` selects a `[from, to]` date range + * - `multiple` selects an arbitrary array of dates + * + * @default 'default' + */ + type?: DatePickerType; + /** + * Initial value when the picker is uncontrolled. + * + * - `default` accepts `Date | string` + * - `range` accepts `[Date, Date]` + * - `multiple` accepts `Date[]` + */ + defaultValue?: Date | [Date, Date] | Date[] | string; + /** + * Controlled selected value. When set, the picker becomes controlled. + */ + value?: Date | [Date, Date] | Date[] | string; + /** + * Render the trigger in an error state. + * + * @default false + */ + error?: boolean; +}; + +/** + * Component for selecting a date with customizable format and placeholder. + * + * Built on top of `react-day-picker` (calendar) composed with Base UI + * `Popover` (positioning + dismiss + a11y). The input/trigger uses our + * standard input-size and input-font-size design tokens. + */ +export const DatePicker = forwardRef(function DatePicker( + { + inputSize = 'medium', + valueFormat = 'DD/MM/YYYY', + placeholder = 'dd/mm/yyyy', + type = 'default', + value, + defaultValue, + error = false, + disabled, + readOnly, + onChange, + minDate, + maxDate, + id, + className, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, + }, + ref, +) { + const isControlled = value !== undefined; + const [internalValue, setInternalValue] = useState(() => + normalizeInitialValue(defaultValue, type), + ); + const [open, setOpen] = useState(false); + // The public value type can't represent a partial range, hence a separate draft. + const [rangeDraft, setRangeDraft] = useState(); + + const currentValue = isControlled ? normalizeInitialValue(value, type) : internalValue; + + const formatToken = useMemo(() => dayjsTokenToDateFns(valueFormat), [valueFormat]); + + const triggerLabel = useMemo( + () => formatTriggerLabel(currentValue, type, formatToken), + [currentValue, type, formatToken], + ); + + const handleChange = useCallback( + (next: Date | [Date, Date] | Date[] | null) => { + if (!isControlled) { + setInternalValue(next); + } + onChange?.(next); + }, + [isControlled, onChange], + ); + + // react-day-picker does not derive the visible month from `selected`. + const defaultMonth = useMemo(() => { + if (currentValue instanceof Date) return currentValue; + if (Array.isArray(currentValue) && currentValue[0] instanceof Date) { + return currentValue[0]; + } + return; + }, [currentValue]); + + const disabledMatcher = useMemo(() => { + const matchers: Matcher[] = []; + if (minDate) matchers.push({ before: minDate }); + if (maxDate) matchers.push({ after: maxDate }); + return matchers.length === 0 ? undefined : matchers; + }, [minDate, maxDate]); + + const triggerClassName = clsx( + inputFontStyles[inputSize], + inputSizeStyles[inputSize], + styles['container'], + { + [styles['container--error']]: error, + [styles['container--placeholder']]: triggerLabel === null, + }, + className, + ); + + const calendar = ( + <> + {type === 'default' && ( + { + handleChange(selected ?? null); + setOpen(false); + }} + disabled={disabledMatcher} + defaultMonth={defaultMonth} + startMonth={minDate} + endMonth={maxDate} + showOutsideDays + weekStartsOn={1} + navLayout="around" + components={{ Chevron: CalendarChevron }} + /> + )} + {type === 'range' && ( + { + if (range?.from && range?.to) { + setRangeDraft(undefined); + handleChange([range.from, range.to]); + setOpen(false); + } else { + setRangeDraft(range); + if (currentValue !== null) { + handleChange(null); + } + } + }} + disabled={disabledMatcher} + defaultMonth={defaultMonth} + startMonth={minDate} + endMonth={maxDate} + showOutsideDays + weekStartsOn={1} + navLayout="around" + components={{ Chevron: CalendarChevron }} + /> + )} + {type === 'multiple' && ( + handleChange(dates ?? [])} + disabled={disabledMatcher} + defaultMonth={defaultMonth} + startMonth={minDate} + endMonth={maxDate} + showOutsideDays + weekStartsOn={1} + navLayout="around" + components={{ Chevron: CalendarChevron }} + /> + )} + + ); + + return ( + { + if (disabled || readOnly) return; + setOpen(nextOpen); + if (!nextOpen) { + setRangeDraft(undefined); + } + }} + > + + {triggerLabel ?? placeholder} + + + + {calendar} + + + + ); +}); + +function CalendarChevron({ + orientation, + className, +}: { + orientation?: 'up' | 'down' | 'left' | 'right'; + className?: string; +}) { + if (orientation === 'left') { + return ; + } + if (orientation === 'right') { + return ; + } + // up/down used by dropdowns — fall back to a right-pointing caret rotated + return ; +} + +function formatTriggerLabel( + value: Date | [Date, Date] | Date[] | null, + type: DatePickerType, + token: string, +): string | null { + if (value == null) return null; + if (type === 'default') { + return value instanceof Date ? format(value, token) : null; + } + if (type === 'range') { + if (!isDateTuple(value)) return null; + const [from, to] = value; + return `${format(from, token)} – ${format(to, token)}`; + } + if (type === 'multiple') { + if (!Array.isArray(value) || value.length === 0) return null; + return value.map((d) => format(d, token)).join(', '); + } + return null; +} + +function toDateRange(value: Date | [Date, Date] | Date[] | null): DateRange | undefined { + if (!isDateTuple(value)) return undefined; + return { from: value[0], to: value[1] }; +} diff --git a/packages/ui/src/components/date-picker/date-utils.spec.ts b/packages/ui/src/components/date-picker/date-utils.spec.ts new file mode 100644 index 000000000..0f9ab23a8 --- /dev/null +++ b/packages/ui/src/components/date-picker/date-utils.spec.ts @@ -0,0 +1,83 @@ +import { dayjsTokenToDateFns, normalizeInitialValue, parseDateValue } from './date-utils'; + +describe('dayjsTokenToDateFns', () => { + it('converts YYYY-MM-DD to yyyy-MM-dd', () => { + expect(dayjsTokenToDateFns('YYYY-MM-DD')).toBe('yyyy-MM-dd'); + }); + + it('converts DD/MM/YYYY to dd/MM/yyyy', () => { + expect(dayjsTokenToDateFns('DD/MM/YYYY')).toBe('dd/MM/yyyy'); + }); + + it('leaves tokens without YYYY/DD unchanged and preserves MM', () => { + expect(dayjsTokenToDateFns('MM')).toBe('MM'); + expect(dayjsTokenToDateFns('HH:mm')).toBe('HH:mm'); + }); +}); + +describe('parseDateValue', () => { + it('parses a YYYY-MM-DD string as a local date', () => { + const result = parseDateValue('2026-01-15'); + expect(result).not.toBeNull(); + expect(result?.getFullYear()).toBe(2026); + expect(result?.getMonth()).toBe(0); + expect(result?.getDate()).toBe(15); + }); + + it('returns null for an invalid string', () => { + expect(parseDateValue('not-a-date')).toBeNull(); + }); + + it('parses a non-date-only string via the native Date constructor', () => { + const result = parseDateValue('2026-01-15T10:30:00Z'); + expect(result).not.toBeNull(); + expect(result?.getTime()).toBe(new Date('2026-01-15T10:30:00Z').getTime()); + }); +}); + +describe('normalizeInitialValue', () => { + it('returns null for null/undefined', () => { + expect(normalizeInitialValue(null, 'default')).toBeNull(); + expect(normalizeInitialValue(undefined, 'default')).toBeNull(); + }); + + describe("type 'default'", () => { + it('passes a Date through unchanged', () => { + const date = new Date(2026, 0, 15); + expect(normalizeInitialValue(date, 'default')).toBe(date); + }); + + it('parses a date string', () => { + const result = normalizeInitialValue('2026-01-15', 'default'); + expect(result).toBeInstanceOf(Date); + expect((result as Date).getFullYear()).toBe(2026); + }); + + it('returns null for a non-date value', () => { + expect(normalizeInitialValue([1, 2] as unknown as Date[], 'default')).toBeNull(); + }); + }); + + describe("type 'range'", () => { + it('returns a 2-Date tuple unchanged', () => { + const tuple: [Date, Date] = [new Date(2026, 0, 1), new Date(2026, 0, 5)]; + expect(normalizeInitialValue(tuple, 'range')).toBe(tuple); + }); + + it('returns null for a non-tuple value', () => { + expect(normalizeInitialValue([new Date(2026, 0, 1)], 'range')).toBeNull(); + expect(normalizeInitialValue(new Date(2026, 0, 1), 'range')).toBeNull(); + }); + }); + + describe("type 'multiple'", () => { + it('returns an array of Dates unchanged', () => { + const dates = [new Date(2026, 0, 1), new Date(2026, 0, 2), new Date(2026, 0, 3)]; + expect(normalizeInitialValue(dates, 'multiple')).toBe(dates); + }); + + it('returns null for a mixed array', () => { + expect(normalizeInitialValue([new Date(2026, 0, 1), 'x'] as unknown as Date[], 'multiple')).toBeNull(); + }); + }); +}); diff --git a/packages/ui/src/components/date-picker/date-utils.ts b/packages/ui/src/components/date-picker/date-utils.ts new file mode 100644 index 000000000..ceb338afa --- /dev/null +++ b/packages/ui/src/components/date-picker/date-utils.ts @@ -0,0 +1,50 @@ +import type { DatePickerType } from './types'; + +const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/; + +export function dayjsTokenToDateFns(token: string): string { + // Convert the most common day.js tokens to date-fns equivalents. We only + // remap the tokens we actually use as defaults; consumers passing a custom + // `valueFormat` are expected to use date-fns tokens (see TSDoc). + return token.replaceAll('YYYY', 'yyyy').replaceAll('DD', 'dd'); + // MM (months) is identical between the two libraries. +} + +export function isDateTuple(value: unknown): value is [Date, Date] { + return Array.isArray(value) && value.length === 2 && value[0] instanceof Date && value[1] instanceof Date; +} + +/** + * Parses a date string into a local `Date`. + * + * A bare `YYYY-MM-DD` string is parsed by the native `Date` constructor as UTC + * midnight, which renders as the previous day in any negative-UTC-offset + * timezone. We build such date-only values in local time instead; any other + * format falls back to the native parser. + */ +export function parseDateValue(raw: string): Date | null { + const match = DATE_ONLY_PATTERN.exec(raw); + const parsed = match ? new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])) : new Date(raw); + return Number.isNaN(parsed.getTime()) ? null : parsed; +} + +export function normalizeInitialValue( + raw: Date | [Date, Date] | Date[] | string | null | undefined, + type: DatePickerType, +): Date | [Date, Date] | Date[] | null { + if (raw == null) return null; + if (type === 'default') { + if (raw instanceof Date) return raw; + if (typeof raw === 'string') { + return parseDateValue(raw); + } + return null; + } + if (type === 'range') { + return isDateTuple(raw) ? raw : null; + } + if (type === 'multiple') { + return Array.isArray(raw) && raw.every((d) => d instanceof Date) ? (raw as Date[]) : null; + } + return null; +} diff --git a/packages/ui/src/components/date-picker/index.ts b/packages/ui/src/components/date-picker/index.ts new file mode 100644 index 000000000..c50327b26 --- /dev/null +++ b/packages/ui/src/components/date-picker/index.ts @@ -0,0 +1,2 @@ +export * from './date-picker'; +export * from './types'; diff --git a/packages/ui/src/components/date-picker/types.ts b/packages/ui/src/components/date-picker/types.ts new file mode 100644 index 000000000..64f2ac58b --- /dev/null +++ b/packages/ui/src/components/date-picker/types.ts @@ -0,0 +1,80 @@ +import type { ItemSize } from '@ui/shared/types/item-size'; + +/** + * The picker type. + * + * - `default`: select a single date. + * - `range`: select a date range (`[from, to]`). + * - `multiple`: select an arbitrary array of dates. + */ +export type DatePickerType = 'default' | 'range' | 'multiple'; + +/** + * The set of value shapes accepted by the date picker, depending on the + * picker type. + * + * - `default` -> `Date | string` + * - `range` -> `[Date, Date]` + * - `multiple`-> `Date[]` + * + * `string` is accepted for `default` for backwards compatibility (parsed via + * the `Date` constructor) and `null`/`undefined` represent "no selection". + */ +export type DatePickerValue = Date | [Date, Date] | Date[] | string | null | undefined; + +/** + * Public props of the {@link DatePicker} component. + * + * The previous, Mantine-based implementation accepted the full + * `DatePickerInputProps`. The current implementation exposes only the subset + * actually consumed in the monorepo, plus standard a11y props. + */ +export type DatePickerProps = { + /** + * Size variant of the trigger input. + * @default 'medium' + */ + inputSize?: ItemSize; + /** + * Disable the picker. The trigger button is not interactive and the + * popover cannot be opened. + */ + disabled?: boolean; + /** + * Render the picker as read-only. The trigger displays the current value + * but the popover cannot be opened. + */ + readOnly?: boolean; + /** + * Callback fired when the selected value changes. + * + * - `default` -> the selected `Date` or `null` when cleared + * - `range` -> `[from, to]` once both dates are selected, otherwise `null` + * - `multiple`-> the array of selected `Date`s (empty array allowed) + */ + onChange?: (value: Date | [Date, Date] | Date[] | null) => void; + /** + * The earliest selectable date (inclusive). + */ + minDate?: Date; + /** + * The latest selectable date (inclusive). + */ + maxDate?: Date; + /** + * `id` attribute applied to the trigger button. + */ + id?: string; + /** + * Class name applied to the trigger button. + */ + className?: string; + /** + * Accessible name for the trigger button. + */ + 'aria-label'?: string; + /** + * `aria-labelledby` for the trigger button. + */ + 'aria-labelledby'?: string; +}; diff --git a/packages/ui/src/components/date-picker/variables.css b/packages/ui/src/components/date-picker/variables.css new file mode 100644 index 000000000..35a08c75b --- /dev/null +++ b/packages/ui/src/components/date-picker/variables.css @@ -0,0 +1,20 @@ +/* react-day-picker's own stylesheet joins `ui.base`, so the `ui.component` + theming in date-picker.module.css beats it by layer order alone - no + specificity tricks, no load-order requirement. */ +@import 'react-day-picker/style.css' layer(ui.base); + +:root { + --ax-public-date-picker-dropdown-background: var(--ax-ui-bg-primary-default); + --ax-public-date-picker-dropdown-border-color: var(--ax-ui-stroke-primary-default); + --ax-public-date-picker-dropdown-box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.16); + + --ax-public-date-picker-color: var(--ax-txt-secondary-default); + --ax-public-date-picker-header-background: var(--ax-ui-bg-secondary-default); + --ax-public-date-picker-date-header-color: var(--ax-txt-tertiary-default); + --ax-public-date-picker-nav-color: var(--ax-txt-secondary-default); + --ax-public-date-picker-date-outside-color: var(--ax-txt-quaternary-default); + --ax-public-date-picker-date-hover-background-color: var(--ax-ui-bg-secondary-default); + --ax-public-date-picker-date-selected-background-color: var(--ax-ui-bg-tertiary-selected); + --ax-public-date-picker-date-selected-color: var(--ax-txt-primary-default); + --ax-public-date-picker-date-today-color: var(--ax-ui-stroke-primary-highlight); +} diff --git a/packages/ui/src/components/edge/edge-label/edge-label-size.module.css b/packages/ui/src/components/edge/edge-label/edge-label-size.module.css new file mode 100644 index 000000000..41ddaf78e --- /dev/null +++ b/packages/ui/src/components/edge/edge-label/edge-label-size.module.css @@ -0,0 +1,101 @@ +:root { + --ax-public-edge-label-medium-label-padding: var(--ax-token-spacing-label-m-v-pad-1) + var(--ax-token-spacing-label-m-h-pad-1); + --ax-public-edge-label-small-label-padding: var(--ax-token-spacing-label-s-v-pad-1) + var(--ax-token-spacing-label-s-h-pad-1); + --ax-public-edge-label-extra-small-label-padding: var(--ax-token-spacing-label-xs-v-pad-1) + var(--ax-token-spacing-label-xs-h-pad-1); + + --ax-public-edge-label-medium-icon-padding: var(--ax-token-spacing-label-m-v-pad-2); + --ax-public-edge-label-small-icon-padding: var(--ax-token-spacing-label-s-v-pad-2); + --ax-public-edge-label-extra-small-icon-padding: var(--ax-token-spacing-label-xs-v-pad-2); + + --ax-public-edge-label-medium-content-padding: var(--ax-token-spacing-label-m-v-pad-2) + var(--ax-token-spacing-label-m-h-pad-2); + --ax-public-edge-label-small-content-padding: var(--ax-token-spacing-label-s-v-pad-2) + var(--ax-token-spacing-label-s-h-pad-2); + --ax-public-edge-label-extra-small-content-padding: var(--ax-token-spacing-label-xs-v-pad-2) + var(--ax-token-spacing-label-xs-h-pad-2); + + --ax-public-edge-label-medium-border-radius: var(--ax-token-radius-label-m); + --ax-public-edge-label-small-border-radius: var(--ax-token-radius-label-s); + --ax-public-edge-label-extra-small-border-radius: var(--ax-token-radius-label-xs); + + --ax-public-edge-label-medium-icon-size: 1.125rem; + --ax-public-edge-label-small-icon-size: 1rem; + --ax-public-edge-label-extra-small-icon-size: 0.875rem; +} + +@layer ui.component { + .medium { + border-radius: var(--ax-public-edge-label-medium-border-radius); + composes: ax-public-edge-label-medium from global; + + svg { + width: var(--ax-public-edge-label-medium-icon-size); + height: var(--ax-public-edge-label-medium-icon-size); + } + } + + .small { + border-radius: var(--ax-public-edge-label-small-border-radius); + composes: ax-public-edge-label-small from global; + + svg { + width: var(--ax-public-edge-label-small-icon-size); + height: var(--ax-public-edge-label-small-icon-size); + } + } + + .extra-small { + border-radius: var(--ax-public-edge-label-extra-small-border-radius); + composes: ax-public-edge-label-extra-small from global; + + svg { + width: var(--ax-public-edge-label-extra-small-icon-size); + height: var(--ax-public-edge-label-extra-small-icon-size); + } + } + + .text { + &.medium { + padding: var(--ax-public-edge-label-medium-label-padding); + } + + &.small { + padding: var(--ax-public-edge-label-small-label-padding); + } + + &.extra-small { + padding: var(--ax-public-edge-label-extra-small-label-padding); + } + } + + .icon { + &.medium { + padding: var(--ax-public-edge-label-medium-icon-padding); + } + + &.small { + padding: var(--ax-public-edge-label-small-icon-padding); + } + + &.extra-small { + padding: var(--ax-public-edge-label-extra-small-icon-padding); + } + } + + .compound { + &.medium { + padding: var(--ax-public-edge-label-medium-content-padding); + } + + &.small { + padding: var(--ax-public-edge-label-small-content-padding); + } + + &.extra-small { + padding: var(--ax-public-edge-label-extra-small-content-padding); + } + } +} diff --git a/packages/ui/src/components/edge/edge-label/edge-label.module.css b/packages/ui/src/components/edge/edge-label/edge-label.module.css new file mode 100644 index 000000000..1fe428e45 --- /dev/null +++ b/packages/ui/src/components/edge/edge-label/edge-label.module.css @@ -0,0 +1,61 @@ +:root { + --ax-public-edge-label-gap: var(--ax-token-spacing-label-gap); + --ax-public-edge-background-color: var(--ax-ui-bg-tertiary-default); + --ax-public-edge-label-color: var(--ax-txt-primary-default); + + --ax-public-edge-label-border-size: var(--ax-token-stroke-edge-regular); + --ax-public-edge-label-border-size-selected: var(--ax-token-stroke-edge-bold); + + --ax-public-edge-label-border-style: solid; + --ax-public-edge-label-border-color: var(--ax-edge-primary-default); + --ax-public-edge-label-border-color-selected: var(--ax-edge-primary-active); + --ax-public-edge-label-border-color-hover: var(--ax-edge-primary-hover); + + --ax-public-edge-label-color-disabled: var(--ax-txt-quaternary-default); + --ax-public-edge-label-border-color-disabled: var(--ax-edge-primary-disabled); +} + +@layer ui.component { + .container { + position: absolute; + display: inline-flex; + justify-content: center; + align-items: center; + gap: var(--ax-public-edge-label-gap); + + pointer-events: auto; + + width: max-content; + + background-color: var(--ax-public-edge-background-color); + color: var(--ax-public-edge-label-color); + + border: var(--ax-public-edge-label-border-size) var(--ax-public-edge-label-border-style) + var(--ax-public-edge-label-border-color); + + transition: border var(--ax-public-transition); + + &:hover, + &.hovered, + &.selected, + &.disabled { + transition: none; + } + + &.hovered:not(.selected, .disabled, .temporary), + &:hover:not(.selected, .disabled, .temporary) { + border-color: var(--ax-public-edge-label-border-color-hover); + } + + &.temporary, + &.selected { + border-width: var(--ax-public-edge-label-border-size-selected); + border-color: var(--ax-public-edge-label-border-color-selected); + } + + &.disabled { + color: var(--ax-public-edge-label-color-disabled); + border-color: var(--ax-public-edge-label-border-color-disabled); + } + } +} diff --git a/packages/ui/src/components/edge/edge-label/edge-label.tsx b/packages/ui/src/components/edge/edge-label/edge-label.tsx new file mode 100644 index 000000000..045f3fa56 --- /dev/null +++ b/packages/ui/src/components/edge/edge-label/edge-label.tsx @@ -0,0 +1,65 @@ +import clsx from 'clsx'; +import { HTMLAttributes, PropsWithChildren, forwardRef } from 'react'; + +import sizeStyles from './edge-label-size.module.css'; +import styles from './edge-label.module.css'; + +import { EdgeLabelSize, EdgeState } from '../types'; + +type EdgeProps = { + size?: EdgeLabelSize; + isHovered?: boolean; + /** + * Determines the layout style for the EdgeLabel based on its content: + * + * text: Simple text label. + * icon: Single icon without additional content. + * compound: Mixed content like icons + text, multiple icons, etc. + */ + type?: EdgeLabelType; + /** + * The visual state of the edge. Determines base styles like `strokeWidth`. + */ + state?: EdgeState; +}; + +type EdgeLabelType = 'text' | 'icon' | 'compound'; + +/* + * A dedicated label component designed for use on an edge within a diagram. + * The `EdgeLabel` component provides a consistent and unified design, following the styling conventions prepared for edges. + * The state of the label (such as hover, selection, and disabled) is managed externally. + * Therefore, dedicated props are exposed to reflect these states visually. + * + * The component extends a standard `HTMLDivElement`, enabling flexibility in layout and positioning. + * For example, you can use CSS transforms or absolute positioning to control the label's placement on the edge. + * The content of the label can be any valid `ReactNode`, allowing full customization. + * + * > **Note:** + * > By default, `EdgeLabel` uses `position: absolute` to properly position the label based on the `labelX` and `labelY` + * parameters provided by ReactFlow. This ensures the label appears at the correct location on the edge. When using `EdgeLabel` + * outside of a diagram context, keep in mind that it may require additional wrapper elements or layout adjustments, + * as its absolute positioning removes it from the normal document flow. + */ +export const EdgeLabel = forwardRef>>( + ({ children, size = 'medium', isHovered, state = 'default', type = 'text', className, ...rest }, ref) => { + return ( +
+ {children} +
+ ); + }, +); diff --git a/packages/ui/src/components/edge/index.ts b/packages/ui/src/components/edge/index.ts new file mode 100644 index 000000000..eeb0b2c16 --- /dev/null +++ b/packages/ui/src/components/edge/index.ts @@ -0,0 +1,3 @@ +export * from './edge-label/edge-label'; +export * from './use-edge-styles/use-edge-styles'; +export * from './types'; diff --git a/packages/ui/src/components/edge/types.ts b/packages/ui/src/components/edge/types.ts new file mode 100644 index 000000000..d5e6b9113 --- /dev/null +++ b/packages/ui/src/components/edge/types.ts @@ -0,0 +1,7 @@ +import { SIZES, Size } from '../../shared/types/size'; +import { rangeBetween } from '../../shared/utils/arrays'; + +export const EDGE_LABEL_SIZES = rangeBetween(SIZES, 'extra-small', 'medium'); +export type EdgeLabelSize = Extract; + +export type EdgeState = 'default' | 'selected' | 'disabled' | 'temporary'; diff --git a/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.css b/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.css new file mode 100644 index 000000000..b6ff1480e --- /dev/null +++ b/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.css @@ -0,0 +1,8 @@ +:root { + --ax-public-edge-color-disabled: var(--ax-edge-primary-disabled); + --ax-public-edge-color-select: var(--ax-edge-primary-active); + --ax-public-edge-color-hover: var(--ax-edge-primary-hover); + --ax-public-edge-color: var(--ax-edge-primary-default); + --ax-public-edge-stroke-width-select: 3; + --ax-public-edge-stroke-width: 2; +} diff --git a/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.tsx b/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.tsx new file mode 100644 index 000000000..e02a3d950 --- /dev/null +++ b/packages/ui/src/components/edge/use-edge-styles/use-edge-styles.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react'; + +import './use-edge-styles.css'; + +import { EdgeState } from '../types'; + +type UseEdgeStyleParams = { + /** + * The visual state of the edge. Determines base styles like `strokeWidth`. + */ + state?: EdgeState; + + /** + * Whether the edge is currently hovered. + * When true, applies hover color on top of the state styles. + */ + isHovered?: boolean; +}; + +/** + * A custom hook for computing CSS style properties for diagram edges based on their visual state and hover interaction. + * + * The `useEdgeStyle` hook returns an object of CSS properties (such as `stroke`, `strokeWidth`, and `transition`) + * that can be directly applied to an SVG path element representing an edge. + */ +export function useEdgeStyle({ state = 'default', isHovered = false }: UseEdgeStyleParams) { + return useMemo(() => { + const strokeCssVariable = + state === 'disabled' + ? '--ax-public-edge-color-disabled' + : state === 'selected' || state === 'temporary' + ? '--ax-public-edge-color-select' + : isHovered + ? '--ax-public-edge-color-hover' + : '--ax-public-edge-color'; + + const strokeWidthCssVariable = + state === 'selected' ? '--ax-public-edge-stroke-width-select' : '--ax-public-edge-stroke-width'; + + const transition = isHovered ? 'none' : `stroke var(--ax-public-transition)`; + + return { + stroke: `var(${strokeCssVariable})`, + strokeWidth: `var(${strokeWidthCssVariable})`, + transition, + }; + }, [state, isHovered]); +} diff --git a/packages/ui/src/components/input/index.ts b/packages/ui/src/components/input/index.ts new file mode 100644 index 000000000..04f79060d --- /dev/null +++ b/packages/ui/src/components/input/index.ts @@ -0,0 +1,2 @@ +export * from './input'; +export * from './types'; diff --git a/packages/ui/src/components/input/input-root.module.css b/packages/ui/src/components/input/input-root.module.css new file mode 100644 index 000000000..75971ce0f --- /dev/null +++ b/packages/ui/src/components/input/input-root.module.css @@ -0,0 +1,35 @@ +@layer ui.component { + .input-root { + display: flex; + align-items: center; + cursor: text; + + color: var(--ax-public-input-root-color); + + border: var(--ax-public-input-root-border-size) solid var(--ax-public-input-root-border-color); + + &:not(:hover) { + transition: all var(--ax-public-transition); + } + + &:has(input:focus-visible) { + border-color: var(--ax-public-input-root-border-color-focus); + } + + &:global(.base--error) { + background-color: var(--ax-public-input-root-background-color-error); + border-color: var(--ax-public-input-root-border-color-error); + color: var(--ax-public-input-root-color-error); + } + + &:global(.base--disabled) { + color: var(--ax-public-input-root-color-disabled); + } + + svg { + width: 1rem; + height: 1rem; + min-width: 1rem; + } + } +} diff --git a/packages/ui/src/components/input/input.module.css b/packages/ui/src/components/input/input.module.css new file mode 100644 index 000000000..32ba45b29 --- /dev/null +++ b/packages/ui/src/components/input/input.module.css @@ -0,0 +1,26 @@ +@layer ui.component { + .input { + border: none; + background: transparent; + padding: 0; + color: unset; + width: 100%; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &:focus-visible { + outline: none; + } + + &:disabled { + color: var(--ax-public-input-color-disabled); + } + + &::placeholder { + color: unset; + opacity: 0.5; + } + } +} diff --git a/packages/ui/src/components/input/input.tsx b/packages/ui/src/components/input/input.tsx new file mode 100644 index 000000000..be57699ce --- /dev/null +++ b/packages/ui/src/components/input/input.tsx @@ -0,0 +1,43 @@ +import { Input as InputBase } from '@base-ui/react/input'; +import clsx from 'clsx'; + +import inputRootStyles from './input-root.module.css'; +import inputStyles from './input.module.css'; +import './variables.css'; +import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; +import inputSizeStyles from '@ui/shared/styles/input-size.module.css'; + +import type { InputProps } from './types'; + +export function Input({ + size = 'medium', + startAdornment, + endAdornment, + error = false, + className, + ...props +}: InputProps) { + return ( +
{ + if (event.target !== event.currentTarget) return; + event.preventDefault(); + event.currentTarget.querySelector('input')?.focus(); + }} + > + {startAdornment} + + {endAdornment} +
+ ); +} diff --git a/packages/ui/src/components/input/types.ts b/packages/ui/src/components/input/types.ts new file mode 100644 index 000000000..5addf3db0 --- /dev/null +++ b/packages/ui/src/components/input/types.ts @@ -0,0 +1,25 @@ +import type { ItemSize } from '@ui/shared/types/item-size'; +import type { InputHTMLAttributes, ReactNode } from 'react'; + +export type InputProps = Omit, 'size'> & { + /** + * Specifies the size of the input field. + * Can be 'small', 'medium', or 'large'. + */ + size?: ItemSize; + + /** + * Element displayed at the end of the input field. + */ + endAdornment?: ReactNode; + + /** + * Element displayed at the start of the input field. + */ + startAdornment?: ReactNode; + + /** + * Renders the input in an error state. + */ + error?: boolean; +}; diff --git a/packages/ui/src/components/input/variables.css b/packages/ui/src/components/input/variables.css new file mode 100644 index 000000000..796386b29 --- /dev/null +++ b/packages/ui/src/components/input/variables.css @@ -0,0 +1,14 @@ +:root { + --ax-public-input-color-disabled: var(--ax-txt-quaternary-default); + + --ax-public-input-root-color: var(--ax-txt-secondary-default); + --ax-public-input-root-border-color: var(--ax-input-stroke-primary-default); + --ax-public-input-root-border-color-focus: var(--ax-input-stroke-primary-focus); + --ax-public-input-root-border-size: 0.0625rem; + + --ax-public-input-root-background-color-error: var(--ax-input-bg-primary-error); + --ax-public-input-root-border-color-error: var(--ax-input-stroke-primary-error); + --ax-public-input-root-color-error: var(--ax-txt-error-default); + + --ax-public-input-root-color-disabled: var(--ax-txt-quaternary-default); +} diff --git a/packages/ui/src/components/menu/index.ts b/packages/ui/src/components/menu/index.ts new file mode 100644 index 000000000..c18d59485 --- /dev/null +++ b/packages/ui/src/components/menu/index.ts @@ -0,0 +1,2 @@ +export * from './menu'; +export * from './types'; diff --git a/packages/ui/src/components/menu/menu-item.tsx b/packages/ui/src/components/menu/menu-item.tsx new file mode 100644 index 000000000..ce807c9b2 --- /dev/null +++ b/packages/ui/src/components/menu/menu-item.tsx @@ -0,0 +1,22 @@ +import { Menu as MenuBase } from '@base-ui/react/menu'; +import clsx from 'clsx'; + +import listItemSize from '@ui/shared/styles/list-item-size.module.css'; +import listItemStyles from '@ui/shared/styles/list-item.module.css'; + +import { MenuItemProps } from './types'; + +export function MenuItem({ icon, label, disabled, destructive, size = 'medium', onClick }: MenuItemProps) { + return ( + + {icon} + {label} + + ); +} diff --git a/packages/ui/src/components/menu/menu.tsx b/packages/ui/src/components/menu/menu.tsx new file mode 100644 index 000000000..213061c1e --- /dev/null +++ b/packages/ui/src/components/menu/menu.tsx @@ -0,0 +1,93 @@ +import { Menu as MenuBase } from '@base-ui/react/menu'; +import { Separator } from '@ui/components/separator/separator'; +import { ItemSize } from '@ui/shared/types/item-size'; +import clsx from 'clsx'; +import { ReactElement, memo } from 'react'; + +import listBoxStyles from '@ui/shared/styles/list-box.module.css'; + +import { MenuItem } from './menu-item'; +import { type OffsetOptions, type Placement, offsetToBaseUI, placementToSideAlign } from './placement'; +import { MenuItemProps } from './types'; + +export type { OffsetOptions, Placement } from './placement'; + +type MenuProps = { + /** + * Array of menu items to be rendered in the menu. + * Each item can be either a regular menu item or a separator. + */ + items: MenuItemProps[]; + + /** + * Size variant for the menu items. + * @default 'medium' + */ + size?: ItemSize; + + /** + * The preferred placement of the menu relative to its trigger element. + * Uses Floating UI placement options. + * @default 'bottom-end' + */ + placement?: Placement | undefined; + + /** + * Controls whether the menu is open or closed. + * When omitted, the menu's open state will be managed internally + * and toggled by clicking on the `children` trigger element. + */ + open?: boolean | undefined; + + /** + * Callback fired when the component requests to be opened or closed. + * Receives the next open state and the native event that triggered the + * change (if any). + */ + onOpenChange?: (open: boolean, event?: Event) => void; + + /** + * Distance between a popup and the trigger element + */ + offset?: OffsetOptions; + /** + * The trigger element that will open the menu when clicked. + * This element will be wrapped in a button with appropriate ARIA attributes. + */ + children?: ReactElement; +}; + +export const Menu = memo( + ({ items, size = 'medium', placement = 'bottom-end', children, open, offset, onOpenChange }: MenuProps) => { + const { side, align } = placementToSideAlign(placement); + const { sideOffset, alignOffset } = offsetToBaseUI(offset, align); + + return ( + onOpenChange(nextOpen, eventDetails.event) : undefined} + > + {children && } + + + + {items.map((item, index) => + item.type === 'separator' ? ( + + ) : ( + + ), + )} + + + + + ); + }, +); diff --git a/packages/ui/src/components/menu/placement.spec.ts b/packages/ui/src/components/menu/placement.spec.ts new file mode 100644 index 000000000..43b15ca7b --- /dev/null +++ b/packages/ui/src/components/menu/placement.spec.ts @@ -0,0 +1,44 @@ +import { offsetToBaseUI, placementToSideAlign } from './placement'; + +describe('placementToSideAlign', () => { + it('defaults align to center for a bare side', () => { + expect(placementToSideAlign('bottom')).toEqual({ side: 'bottom', align: 'center' }); + }); + + it('splits a side-start placement', () => { + expect(placementToSideAlign('top-start')).toEqual({ side: 'top', align: 'start' }); + }); + + it('splits a side-end placement', () => { + expect(placementToSideAlign('right-end')).toEqual({ side: 'right', align: 'end' }); + }); +}); + +describe('offsetToBaseUI', () => { + it('returns an empty object for undefined offset', () => { + expect(offsetToBaseUI(undefined, 'center')).toEqual({}); + }); + + it('maps a number offset to sideOffset', () => { + expect(offsetToBaseUI(8, 'center')).toEqual({ sideOffset: 8 }); + }); + + it('negates crossAxis for end alignment', () => { + expect(offsetToBaseUI({ mainAxis: 4, crossAxis: 6 }, 'end')).toEqual({ sideOffset: 4, alignOffset: -6 }); + }); + + it('keeps crossAxis sign for start alignment', () => { + expect(offsetToBaseUI({ mainAxis: 4, crossAxis: 6 }, 'start')).toEqual({ sideOffset: 4, alignOffset: 6 }); + }); + + it('keeps crossAxis sign for center alignment', () => { + expect(offsetToBaseUI({ mainAxis: 4, crossAxis: 6 }, 'center')).toEqual({ sideOffset: 4, alignOffset: 6 }); + }); + + it('lets alignmentAxis override the crossAxis-derived value', () => { + expect(offsetToBaseUI({ mainAxis: 4, crossAxis: 6, alignmentAxis: 2 }, 'end')).toEqual({ + sideOffset: 4, + alignOffset: 2, + }); + }); +}); diff --git a/packages/ui/src/components/menu/placement.ts b/packages/ui/src/components/menu/placement.ts new file mode 100644 index 000000000..a37dab6b9 --- /dev/null +++ b/packages/ui/src/components/menu/placement.ts @@ -0,0 +1,39 @@ +import { Menu as MenuBase } from '@base-ui/react/menu'; +import { type ComponentProps } from 'react'; + +type Side = 'top' | 'bottom' | 'left' | 'right'; +type Align = 'start' | 'end'; + +export type Placement = Side | `${Side}-${Align}`; + +type OffsetAxes = { + mainAxis?: number; + crossAxis?: number; + alignmentAxis?: number | null; +}; + +export type OffsetOptions = number | OffsetAxes; + +type PositionerProps = ComponentProps; +type PositionerSide = NonNullable; +type PositionerAlign = NonNullable; + +export function placementToSideAlign(placement: Placement): { + side: PositionerSide; + align: PositionerAlign; +} { + const [side, alignRaw] = placement.split('-') as [PositionerSide, PositionerAlign | undefined]; + return { side, align: alignRaw ?? 'center' }; +} + +export function offsetToBaseUI( + offset: OffsetOptions | undefined, + align: PositionerAlign, +): { sideOffset?: number; alignOffset?: number } { + if (offset == null) return {}; + if (typeof offset === 'number') return { sideOffset: offset }; + const { mainAxis, crossAxis, alignmentAxis } = offset; + const physicalCross = crossAxis ?? 0; + const alignOffset = alignmentAxis ?? (align === 'end' ? -physicalCross : physicalCross); + return { sideOffset: mainAxis ?? 0, alignOffset }; +} diff --git a/packages/ui/src/components/menu/types.ts b/packages/ui/src/components/menu/types.ts new file mode 100644 index 000000000..41a74729a --- /dev/null +++ b/packages/ui/src/components/menu/types.ts @@ -0,0 +1,8 @@ +import { ItemSize } from '@ui/shared/types/item-size'; +import { ListItem } from '@ui/shared/types/list-item'; + +export type MenuItemProps = ListItem & { + destructive?: boolean; + onClick?: () => void; + size?: ItemSize; +}; diff --git a/packages/ui/src/components/modal/index.ts b/packages/ui/src/components/modal/index.ts new file mode 100644 index 000000000..d9e303ab1 --- /dev/null +++ b/packages/ui/src/components/modal/index.ts @@ -0,0 +1,2 @@ +export * from './modal'; +export * from './types'; diff --git a/packages/ui/src/components/modal/modal.module.css b/packages/ui/src/components/modal/modal.module.css new file mode 100644 index 000000000..0ce3ecdec --- /dev/null +++ b/packages/ui/src/components/modal/modal.module.css @@ -0,0 +1,203 @@ +:root { + --ax-public-modal-width: 26.25rem; + --ax-public-modal-border-radius: var(--ax-token-radius-modal-m); + --ax-public-modal-padding: var(--ax-token-spacing-modal-m-header-pad); + + --ax-public-modal-description-size: 0.625rem; + --ax-public-modal-description-line-height: 140%; + + --ax-public-modal-icon-padding: var(--ax-token-spacing-button-m-v-pad-2); + --ax-public-modal-content-padding: var(--ax-token-spacing-modal-m-content-v-pad) + var(--ax-token-spacing-modal-m-content-h-pad); + --ax-public-modal-footer-padding: 0px var(--ax-token-spacing-modal-m-header-pad) + var(--ax-token-spacing-modal-m-header-pad) var(--ax-token-spacing-modal-m-header-pad); + + --ax-public-modal-background: var(--ax-ui-bg-primary-default); + --ax-public-modal-backdrop-background: var(--ax-colors-gray-900); + --ax-public-modal-close-button-color: var(--ax-nav-button-icon-primary-default); + --ax-public-modal-header-background: var(--ax-ui-bg-secondary-default); + --ax-public-modal-header-border-color: var(--ax-ui-stroke-primary-default); + --ax-public-modal-title-color: var(--ax-txt-primary-default); + --ax-public-modal-description-color: var(--ax-txt-secondary-default); + --ax-public-modal-icon-background: var(--ax-button-primary-bg-default); + --ax-public-modal-icon-color: var(--ax-txt-primary-white); + --ax-public-modal-content-background: var(--ax-ui-bg-primary-default); + --ax-public-modal-footer-border-color: var(--ax-ui-stroke-primary-default); + + --ax-public-modal-large-width: 30rem; + --ax-public-modal-large-padding: var(--ax-token-spacing-modal-l-header-pad); + --ax-public-modal-large-title-size: 1.125rem; + --ax-public-modal-large-title-line-height: 130%; + --ax-public-modal-large-icon-padding: var(--ax-token-spacing-button-l-v-pad-2); + + --ax-public-modal-box-shadow: var(--ax-token-shadow-shadow-l-x) var(--ax-token-shadow-shadow-l-y) + var(--ax-token-shadow-shadow-l-blur) var(--ax-token-shadow-shadow-l-spread) var(--ax-shadow); + + --ax-public-modal-gap: var(--ax-token-spacing-modal-m-header-gap); + --ax-public-modal-gap-large: var(--ax-token-spacing-modal-l-header-gap); + + --ax-public-modal-icon-border-radius: var(--ax-token-radius-button-m); + --ax-public-modal-icon-border-radius-large: var(--ax-token-radius-button-l); + + --ax-public-modal-content-gap: 0.25rem; +} + +@layer ui.component { + .backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + /* Just below the dialog window: at a lower value the host's own panels + stack above it, so they stay undimmed and swallow the clicks that should + dismiss the dialog. */ + z-index: 1000; + inset: 0; + background-color: color-mix(in srgb, var(--ax-public-modal-backdrop-background), transparent 50%); + + transition: opacity 200ms ease; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + } + } + + .modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1001; + display: flex; + flex-direction: column; + max-height: calc(100dvh - 3rem); + background-color: var(--ax-public-modal-background); + border-radius: var(--ax-public-modal-border-radius); + width: var(--ax-public-modal-width); + outline: none; + box-shadow: var(--ax-public-modal-box-shadow); + + transition: + opacity 200ms ease, + transform 200ms ease; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: translate(-50%, -50%) scale(0.96); + } + } + + .header { + display: flex; + flex-shrink: 0; + padding: var(--ax-public-modal-padding); + justify-content: space-between; + align-items: center; + border-radius: var(--ax-public-modal-border-radius) var(--ax-public-modal-border-radius) 0 0; + border-bottom: 1px solid var(--ax-public-modal-header-border-color); + background: var(--ax-public-modal-header-background); + } + + .title-wrapper { + display: flex; + align-items: center; + gap: var(--ax-token-spacing-modal-m-header-gap); + } + + .title-container { + display: flex; + flex-direction: column; + gap: var(--ax-token-spacing-modal-m-header-gap); + } + + .icon { + display: flex; + padding: var(--ax-public-modal-icon-padding); + justify-content: center; + align-items: center; + border-radius: var(--ax-public-modal-icon-border-radius); + background: var(--ax-public-modal-icon-background); + color: var(--ax-public-modal-icon-color); + } + + .title { + margin: 0; + color: var(--ax-public-modal-title-color); + } + + .description { + color: var(--ax-public-modal-description-color); + font-size: var(--ax-public-modal-description-size); + font-style: normal; + font-weight: 400; + line-height: var(--ax-public-modal-description-line-height); + margin: 0; + } + + .content { + display: flex; + padding: var(--ax-public-modal-content-padding); + flex-direction: column; + justify-content: center; + align-items: center; + gap: var(--ax-public-modal-content-gap); + align-self: stretch; + min-height: 0; + overflow: auto; + background: var(--ax-public-modal-content-background); + border-radius: var(--ax-public-modal-border-radius); + } + + .footer { + display: flex; + flex-shrink: 0; + padding: var(--ax-public-modal-footer-padding); + justify-content: space-between; + align-items: center; + align-self: stretch; + + &.separated { + border-top: 1px solid var(--ax-public-modal-footer-border-color); + } + } + + .large { + width: var(--ax-public-modal-large-width); + + .header { + padding: var(--ax-public-modal-large-padding); + + .title-wrapper { + gap: var(--ax-public-modal-gap-large); + } + } + + .title { + font-size: var(--ax-public-modal-large-title-size); + line-height: var(--ax-public-modal-large-title-line-height); + } + + .icon { + padding: var(--ax-public-modal-large-icon-padding); + border-radius: var(--ax-public-modal-icon-border-radius-large); + } + + .content { + padding: var(--ax-public-modal-large-padding); + } + + .footer { + &.separated { + padding: var(--ax-public-modal-large-padding); + } + + &.integrated { + padding: 0 var(--ax-public-modal-large-padding) var(--ax-public-modal-large-padding) + var(--ax-public-modal-large-padding); + } + } + } +} diff --git a/packages/ui/src/components/modal/modal.tsx b/packages/ui/src/components/modal/modal.tsx new file mode 100644 index 000000000..953d33a9e --- /dev/null +++ b/packages/ui/src/components/modal/modal.tsx @@ -0,0 +1,112 @@ +import { Dialog } from '@base-ui/react/dialog'; +import { X } from '@phosphor-icons/react'; +import { NavButton } from '@ui/components/button/nav-button/nav-button'; +import type { WithIcon } from '@ui/shared/types/with-icon'; +import clsx from 'clsx'; +import { type ReactNode, forwardRef } from 'react'; + +import styles from './modal.module.css'; + +import type { FooterVariant } from './types'; + +type ModalProps = React.DetailedHTMLProps, HTMLDivElement> & + Partial & { + /** + * Title displayed in the modal header + */ + title: string; + /** + * Optional subtitle displayed below the title + */ + subtitle?: string; + /** + * Content to be displayed in the modal body + */ + children?: ReactNode; + /** + * Content to be displayed in the modal footer + */ + footer?: ReactNode; + /** + * Size variant of the modal + */ + size?: 'regular' | 'large'; + /** + * Variant of the footer styling + */ + footerVariant?: FooterVariant; + /** + * Controls the visibility of the modal + */ + open: boolean; + /** + * Callback function called when the modal is closed + */ + onClose?: () => void; + }; + +/** + * A modal dialog component that appears on top of the main content, + */ +export const Modal = forwardRef( + ( + { + icon, + title, + subtitle, + children, + footer, + size = 'regular', + footerVariant = 'integrated', + open, + onClose, + className, + ...rest + }, + ref, + ) => { + return ( + { + if (!nextOpen) { + onClose?.(); + } + }} + > + + + {/* The popup is the dialog window itself, not a full-screen wrapper: + Base UI closes on a press outside the popup, and a wrapper + covering the viewport would swallow every backdrop click. */} + +
+
+ {icon &&
{icon}
} +
+ }> + {title} + + {subtitle && ( + }> + {subtitle} + + )} +
+
+ {onClose && ( + + + + )} +
+ + {children &&
{children}
} + + {footer &&
{footer}
} +
+
+
+ ); + }, +); diff --git a/packages/ui/src/components/modal/types.ts b/packages/ui/src/components/modal/types.ts new file mode 100644 index 000000000..4ccefb6f3 --- /dev/null +++ b/packages/ui/src/components/modal/types.ts @@ -0,0 +1 @@ +export type FooterVariant = 'integrated' | 'separated'; diff --git a/packages/ui/src/components/node/index.ts b/packages/ui/src/components/node/index.ts new file mode 100644 index 000000000..c4739f512 --- /dev/null +++ b/packages/ui/src/components/node/index.ts @@ -0,0 +1,4 @@ +export * from './node-as-port-wrapper/node-as-port-wrapper'; +export * from './node-description/node-description'; +export * from './node-icon/node-icon'; +export * from './node-panel/node-panel'; diff --git a/packages/ui/src/components/node/node-as-port-wrapper/node-as-port-wrapper.tsx b/packages/ui/src/components/node/node-as-port-wrapper/node-as-port-wrapper.tsx new file mode 100644 index 000000000..cd430ca53 --- /dev/null +++ b/packages/ui/src/components/node/node-as-port-wrapper/node-as-port-wrapper.tsx @@ -0,0 +1,61 @@ +import clsx from 'clsx'; +import { PropsWithChildren, memo, useCallback, useRef, useState } from 'react'; + +import './node-as-port.css'; + +type Position = 'left' | 'top' | 'right' | 'bottom'; + +type Props = { + isConnecting: boolean; + targetPortPosition: Position; + offset?: { + x?: number; + y?: number; + }; +}; + +export const NodeAsPortWrapper = memo(function NodeAsPortWrapper({ + isConnecting, + offset = { x: 0, y: 0 }, + targetPortPosition, + children, +}: PropsWithChildren) { + const ref = useRef(null); + const [isConnectionTarget, setIsConnectionTarget] = useState(false); + const canApplyStyles = isConnecting && isConnectionTarget; + + const containerStyles = canApplyStyles + ? { + '--ax-node-as-port-width': `${ref.current?.offsetWidth}px`, + '--ax-node-as-port-height': `${ref.current?.offsetHeight}px`, + '--ax-node-as-port-position': + targetPortPosition === 'left' + ? `translate(calc(-10% + ${offset.x}px), calc(-50% + ${offset.y}px))` + : `translate(calc(-50% + ${offset.x}px), calc(-10% + ${offset.y}px))`, + } + : null; + + const onMouseEnter = useCallback(() => { + if (isConnecting) { + setIsConnectionTarget(true); + } + }, [isConnecting]); + + const onMouseLeave = useCallback(() => { + setIsConnectionTarget(false); + }, []); + + return ( +
+ {children} +
+ ); +}); diff --git a/packages/ui/src/components/node/node-as-port-wrapper/node-as-port.css b/packages/ui/src/components/node/node-as-port-wrapper/node-as-port.css new file mode 100644 index 000000000..3e8916b96 --- /dev/null +++ b/packages/ui/src/components/node/node-as-port-wrapper/node-as-port.css @@ -0,0 +1,10 @@ +@layer ui.component { + .is-connection-target .react-flow__handle.target::before { + content: ''; + position: absolute; + width: var(--ax-node-as-port-width); + height: var(--ax-node-as-port-height); + transform: var(--ax-node-as-port-position); + border-radius: 0; + } +} diff --git a/packages/ui/src/components/node/node-description/node-description.module.css b/packages/ui/src/components/node/node-description/node-description.module.css new file mode 100644 index 000000000..b6afffd6f --- /dev/null +++ b/packages/ui/src/components/node/node-description/node-description.module.css @@ -0,0 +1,27 @@ +:root { + --ax-public-node-title-color: var(--ax-txt-primary-default); + --ax-public-node-title-subtitle: var(--ax-txt-secondary-default); +} + +@layer ui.component { + .container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + overflow: hidden; + width: 100%; + + .title { + color: var(--ax-public-node-title-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + } + + .subtitle { + color: var(--ax-public-node-title-subtitle); + } + } +} diff --git a/packages/ui/src/components/node/node-description/node-description.tsx b/packages/ui/src/components/node/node-description/node-description.tsx new file mode 100644 index 000000000..c4edb22ca --- /dev/null +++ b/packages/ui/src/components/node/node-description/node-description.tsx @@ -0,0 +1,18 @@ +import clsx from 'clsx'; + +import styles from './node-description.module.css'; + +type Props = { + label: string; + description?: string; + className?: string; +}; + +export function NodeDescription({ label, description, className }: Props) { + return ( +
+ {label} + {description} +
+ ); +} diff --git a/packages/ui/src/components/node/node-icon/node-icon.module.css b/packages/ui/src/components/node/node-icon/node-icon.module.css new file mode 100644 index 000000000..91926b4d5 --- /dev/null +++ b/packages/ui/src/components/node/node-icon/node-icon.module.css @@ -0,0 +1,23 @@ +:root { + --ax-public-node-icon-border-size: 0.0625rem; + --ax-public-node-icon-border-radius: var(--ax-token-radius-node-head-icon); + --ax-public-node-icon-padding: var(--ax-token-spacing-node-head-icon); + + --ax-public-node-icon-color: var(--ax-node-icon-primary-default); + --ax-public-node-icon-container-border-color: var(--ax-node-stroke-primary-default); + --ax-public-node-icon-container-background-color: var(--ax-node-bg-secondary-default); +} + +@layer ui.component { + .container { + display: flex; + padding: var(--ax-public-node-icon-padding); + justify-content: center; + align-items: center; + color: var(--ax-public-node-icon-color); + + border-radius: var(--ax-public-node-icon-border-radius); + border: var(--ax-public-node-icon-border-size) solid var(--ax-public-node-icon-container-border-color); + background: var(--ax-public-node-icon-container-background-color); + } +} diff --git a/packages/ui/src/components/node/node-icon/node-icon.tsx b/packages/ui/src/components/node/node-icon/node-icon.tsx new file mode 100644 index 000000000..3487ec861 --- /dev/null +++ b/packages/ui/src/components/node/node-icon/node-icon.tsx @@ -0,0 +1,13 @@ +import clsx from 'clsx'; +import { ReactNode } from 'react'; + +import styles from './node-icon.module.css'; + +type Props = { + icon: ReactNode; + className?: string; +}; + +export function NodeIcon({ icon, className }: Props) { + return
{icon}
; +} diff --git a/packages/ui/src/components/node/node-panel/handle.module.css b/packages/ui/src/components/node/node-panel/handle.module.css new file mode 100644 index 000000000..4ae8d6a55 --- /dev/null +++ b/packages/ui/src/components/node/node-panel/handle.module.css @@ -0,0 +1,104 @@ +:root { + --ax-public-node-port-boundary-size: 3.125rem; + --ax-public-node-port-size: 0.25rem; + --ax-public-node-port-size-hover: 0.75rem; + --ax-public-node-port-border-size: 0.125rem; + + --ax-public-node-port-background-color: var(--ax-node-port-fill-default); + --ax-public-node-port-border-color: var(--ax-node-port-stroke-default); + + --ax-public-node-port-background-color-hover: var(--ax-node-port-fill-active); + --ax-public-node-port-border-color-hover: var(--ax-node-port-stroke-active); +} + +@layer ui.component { + .handle-wrapper { + & .header { + position: relative; + + :global(:is(.react-flow__handle.react-flow__handle-right, .react-flow__handle.react-flow__handle-left)) { + top: calc(var(--ax-public-node-icon-padding) + var(--ax-public-node-icon-border-size) + 0.75rem); + } + :global(.react-flow__handle.react-flow__handle-right) { + box-sizing: content-box; + right: calc(-1 * (var(--ax-public-node-padding) + var(--ax-public-node-border-size))); + } + + :global(.react-flow__handle.react-flow__handle-left) { + box-sizing: content-box; + left: calc(-1 * (var(--ax-public-node-padding) + var(--ax-public-node-border-size))); + } + } + + :global(.react-flow__handle.react-flow__handle-top) { + box-sizing: content-box; + top: 0; + transform: translate(-50%, -50%); + } + + :global(.react-flow__handle.react-flow__handle-bottom) { + box-sizing: content-box; + bottom: 0; + transform: translate(-50%, 50%); + } + + :global(.react-flow__handle) { + box-sizing: content-box; + width: var(--ax-public-node-port-size); + height: var(--ax-public-node-port-size); + + background: var(--ax-public-node-port-background-color); + border: var(--ax-public-node-port-border-size) solid var(--ax-public-node-port-border-color); + + transition-property: background-color, border-color, width, height; + transition-timing-function: ease-in-out; + transition-duration: 0.1s; + } + + :global(.react-flow__handle::before) { + content: ''; + + position: absolute; + width: var(--ax-public-node-port-boundary-size); + height: var(--ax-public-node-port-boundary-size); + + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + border-radius: 50%; + } + + /* State rules repeat the base rule's content-box opt-out: the build + injects border-box per rule, so an undeclared box-sizing here would + shrink the handle in exactly these states. */ + :global(.connectingfrom) { + box-sizing: content-box; + background: var(--ax-public-node-port-background-color-hover); + border-color: var(--ax-public-node-port-border-color-hover); + + width: var(--ax-public-node-port-size-hover); + height: var(--ax-public-node-port-size-hover); + } + + :global(.react-flow__handle) { + &:hover { + box-sizing: content-box; + background-color: var(--ax-public-node-port-background-color-hover); + border-color: var(--ax-public-node-port-border-color-hover); + + width: var(--ax-public-node-port-size-hover); + height: var(--ax-public-node-port-size-hover); + } + } + + &:hover :global(.react-flow__handle.connectionindicator) { + box-sizing: content-box; + background-color: var(--ax-public-node-port-background-color-hover); + border-color: var(--ax-public-node-port-border-color-hover); + + width: var(--ax-public-node-port-size-hover); + height: var(--ax-public-node-port-size-hover); + } + } +} diff --git a/packages/ui/src/components/node/node-panel/node-panel.module.css b/packages/ui/src/components/node/node-panel/node-panel.module.css new file mode 100644 index 000000000..b1b085f13 --- /dev/null +++ b/packages/ui/src/components/node/node-panel/node-panel.module.css @@ -0,0 +1,74 @@ +:root { + --ax-public-node-width: 16.125rem; + --ax-public-node-height: 100%; + --ax-public-node-border-size: 0.0625rem; + + --ax-public-node-padding: var(--ax-token-spacing-node-head-v-pad); + --ax-public-node-gap: var(--ax-token-spacing-node-head-gap); + --ax-public-node-border-radius: var(--ax-token-radius-node-head); + + --ax-public-node-border-color: var(--ax-node-stroke-primary-default); + --ax-public-node-background-color: var(--ax-node-bg-primary-default); + --ax-public-node-hover-background-color: var(--ax-node-bg-primary-default); + --ax-public-node-border-hover-color: var(--ax-node-stroke-primary-hover); + + --ax-public-node-border-color-selected: var(--ax-node-stroke-primary-hover); + --ax-public-node-box-shadow-selected: var(--ax-token-shadow-focus-node-active-x) + var(--ax-token-shadow-focus-node-active-y) var(--ax-token-shadow-focus-node-active-blur) + var(--ax-token-shadow-focus-node-active-spread) var(--ax-focus-ring-node-active); +} + +@layer ui.component { + .node-panel-wrapper:hover .container { + background: var(--ax-public-node-hover-background-color); + border-color: var(--ax-public-node-border-hover-color); + } + + .container { + display: flex; + width: var(--ax-public-node-width); + height: var(--ax-public-node-height); + padding: var(--ax-public-node-padding); + gap: var(--ax-public-node-gap); + align-items: center; + flex-direction: column; + + border-radius: var(--ax-public-node-border-radius); + border: var(--ax-public-node-border-size) solid var(--ax-public-node-border-color); + background: var(--ax-public-node-background-color); + + &.selected { + border-color: var(--ax-public-node-border-color-selected); + box-shadow: var(--ax-public-node-box-shadow-selected); + } + + &:not(:hover) { + transition: + border-color var(--wb-transition), + box-shadow var(--wb-transition); + } + + .header-wrapper { + width: 100%; + } + + .header-container { + display: flex; + gap: 0.5rem; + align-items: center; + } + + .header-container, + .content-container { + width: 100%; + } + + .header-container, + .content-container, + .handles-container { + &:empty { + display: none; + } + } + } +} diff --git a/packages/ui/src/components/node/node-panel/node-panel.tsx b/packages/ui/src/components/node/node-panel/node-panel.tsx new file mode 100644 index 000000000..c579a2b9d --- /dev/null +++ b/packages/ui/src/components/node/node-panel/node-panel.tsx @@ -0,0 +1,139 @@ +import clsx from 'clsx'; +import { Children, ComponentType, PropsWithChildren, ReactElement, isValidElement, memo, useMemo } from 'react'; + +import handleStyles from './handle.module.css'; +import nodeStyles from './node-panel.module.css'; + +type Props = { + /** Whether the node panel is selected */ + selected: boolean; + /** The content of the node panel */ + children?: React.ReactNode; + /** css className of the node panel */ + className?: string; +}; + +/** + * Node Panel component + * + * This component ensures a structured layout with optional slots: + * - `NodePanel.Header`: A container for the node's header (at most 1). + * - `NodePanel.Content`: A container for the node's main content (at most 1). + * - `NodePanel.Handles`: A container for action handles (at most 1). + * + * **Usage Example** + * ```tsx + * + * Header Content + * Main Content + * Handles + * + * ``` + * + * **Allowed Combinations:** + * - No children (empty node) + * - Header only, Content only, Handles only + * - Any combination of Header, Content, and Handles (but max 1 each) + * + * **Invalid Cases (logged via `console.error`, not thrown):** + * Validation is count-based: it compares the number of children against the + * recognized slots, so any extra child - whether an unknown element or a + * duplicate Header / Content / Handles - trips the same "Unknown children + * detected" error. The panel still renders the slots it found. + */ + +const Header = memo(function Header({ children, className }: PropsWithChildren<{ className?: string }>) { + return
{children}
; +}); + +const Content = memo(function Content({ + children, + className, + isVisible = true, +}: PropsWithChildren<{ className?: string; isVisible?: boolean }>) { + return isVisible &&
{children}
; +}); + +const Handles = memo(function Handles({ + children, + isVisible = true, +}: PropsWithChildren<{ + isVisible?: boolean; + alignment?: 'center' | 'header'; +}>) { + return <>{isVisible && children}; +}); + +const Root = memo(function Root({ selected, children, className }: Props) { + const { headerComponent, contentComponent, handlesComponent, handlesAlignment, hasHandles } = useMemo(() => { + const childrenArray = Children.toArray(children); + + const headerComponent = findChild(childrenArray, NodePanel.Header); + const contentComponent = findChild(childrenArray, NodePanel.Content); + const handlesComponent = findChild(childrenArray, NodePanel.Handles); + + validateChildren(childrenArray, headerComponent, contentComponent, handlesComponent); + + const hasHandles = !!handlesComponent; + const handlesAlignment = handlesComponent?.props.alignment || 'center'; + + return { + headerComponent, + contentComponent, + handlesComponent, + handlesAlignment, + hasHandles, + }; + }, [children]); + + return ( +
+
+
+ {headerComponent} + {handlesComponent} +
+ {contentComponent} +
+
+ ); +}); + +export const NodePanel = { + Root, + Header, + Content, + Handles, +}; + +function findChild( + childrenArray: ReturnType, + element: ComponentType, +): ReactElement | undefined { + return childrenArray.find((child): child is ReactElement => isValidElement(child) && child.type === element); +} + +function validateChildren( + childrenArray: ReturnType, + header: React.ReactNode, + content: React.ReactNode, + handles: React.ReactNode, +) { + const totalValidChildren = (header ? 1 : 0) + (content ? 1 : 0) + (handles ? 1 : 0); + const totalChildren = childrenArray.length; + + if (totalChildren > totalValidChildren) { + console.error( + `NodePanel.Root: Unknown children detected. Only NodePanel.Header, NodePanel.Content, and NodePanel.Handles are allowed. ` + + `Each of these components can be used 0 or 1 time only.`, + ); + } +} diff --git a/packages/ui/src/components/radio-button/index.ts b/packages/ui/src/components/radio-button/index.ts new file mode 100644 index 000000000..1140e08e2 --- /dev/null +++ b/packages/ui/src/components/radio-button/index.ts @@ -0,0 +1 @@ +export * from './radio'; diff --git a/packages/ui/src/components/radio-button/radio-size.module.css b/packages/ui/src/components/radio-button/radio-size.module.css new file mode 100644 index 000000000..02ad89204 --- /dev/null +++ b/packages/ui/src/components/radio-button/radio-size.module.css @@ -0,0 +1,41 @@ +:root { + --ax-public-radio-size-medium: 1.25rem /* missing token */; + --ax-public-radio-size-small: 1.125rem /* missing token */; + --ax-public-radio-size-extra-small: 1rem /* missing token */; + + --ax-public-radio-dot-size-medium: 0.625rem /* missing token */; + --ax-public-radio-dot-size-small: 0.5rem /* missing token */; + --ax-public-radio-dot-size-extra-small: 0.5rem /* missing token */; +} + +@layer ui.component { + .medium { + width: var(--ax-public-radio-size-medium); + height: var(--ax-public-radio-size-medium); + + &::after { + width: var(--ax-public-radio-dot-size-medium); + height: var(--ax-public-radio-dot-size-medium); + } + } + + .small { + width: var(--ax-public-radio-size-small); + height: var(--ax-public-radio-size-small); + + &::after { + width: var(--ax-public-radio-dot-size-small); + height: var(--ax-public-radio-dot-size-small); + } + } + + .extra-small { + width: var(--ax-public-radio-size-extra-small); + height: var(--ax-public-radio-size-extra-small); + + &::after { + width: var(--ax-public-radio-dot-size-extra-small); + height: var(--ax-public-radio-dot-size-extra-small); + } + } +} diff --git a/packages/ui/src/components/radio-button/radio.module.css b/packages/ui/src/components/radio-button/radio.module.css new file mode 100644 index 000000000..6d8b8c20c --- /dev/null +++ b/packages/ui/src/components/radio-button/radio.module.css @@ -0,0 +1,74 @@ +:root { + --ax-public-radio-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-radio-bg: var(--ax-ui-bg-primary-default); + --ax-public-radio-checked-bg: var(--ax-ui-bg-tertiary-selected); + --ax-public-radio-dot-color: var(--ax-colors-gray-100); /* missing token */ + + --ax-public-radio-focus-border-color: var(--ax-txt-primary-default); + + --ax-public-radio-disabled-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-radio-disabled-bg: var(--ax-ui-bg-tertiary-default); + --ax-public-radio-disabled-dot-color: var(--ax-nav-button-icon-primary-disabled); + --ax-public-radio-border-radius: var(--ax-token-radius-radiobutton); +} + +@layer ui.component { + .wrapper { + position: relative; + display: inline-flex; + cursor: pointer; + } + + .radio { + appearance: none; + position: relative; + margin: 0; + border: 0.0625rem solid var(--ax-public-radio-border-color); + border-radius: var(--ax-public-radio-border-radius); + background-color: var(--ax-public-radio-bg); + cursor: pointer; + vertical-align: middle; + transition: all var(--ax-public-transition); + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: var(--ax-public-radio-dot-size-small); + height: var(--ax-public-radio-dot-size-small); + border-radius: 50%; + background-color: var(--ax-public-radio-dot-color); + opacity: 0; + } + + &:checked { + border-color: var(--ax-public-radio-checked-bg); + background-color: var(--ax-public-radio-checked-bg); + + &::after { + opacity: 1; + } + } + + &:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--ax-public-radio-focus-border-color); + } + + &:disabled { + cursor: not-allowed; + background-color: var(--ax-public-radio-disabled-bg); + border-color: var(--ax-public-radio-disabled-border-color); + + &:checked { + background-color: var(--ax-public-radio-disabled-bg); + + &::after { + background-color: var(--ax-public-radio-disabled-dot-color); + } + } + } + } +} diff --git a/packages/ui/src/components/radio-button/radio.tsx b/packages/ui/src/components/radio-button/radio.tsx new file mode 100644 index 000000000..7842a5cb4 --- /dev/null +++ b/packages/ui/src/components/radio-button/radio.tsx @@ -0,0 +1,42 @@ +import { SelectorSize } from '@ui/shared/types/selector-size'; +import clsx from 'clsx'; +import { InputHTMLAttributes } from 'react'; + +import radioButtonSizeStyles from './radio-size.module.css'; +import radioButtonStyles from './radio.module.css'; + +type Props = { + /** The size of the radio button */ + size?: SelectorSize; + /** Whether the radio button is checked */ + checked?: boolean; + /** The name of the radio button group */ + name: string; + /** The value of the radio button */ + value: string | number; + /** Callback fired when the radio button state changes */ + onChange?: (event: React.ChangeEvent) => void; +} & Omit, 'size' | 'type' | 'value' | 'name'>; + +/** + * A radio button component that allows users to select a single option from a group. + */ +export function Radio({ size = 'medium', className, checked, name, value, onChange, ...props }: Props) { + function handleChange(event: React.ChangeEvent) { + onChange?.(event); + } + + return ( + + ); +} diff --git a/packages/ui/src/components/segment-picker/border-radius-size.module.css b/packages/ui/src/components/segment-picker/border-radius-size.module.css new file mode 100644 index 000000000..e8bf82d6e --- /dev/null +++ b/packages/ui/src/components/segment-picker/border-radius-size.module.css @@ -0,0 +1,39 @@ +:root { + --ax-public-segment-picker-border-radius-extra-large: var(--ax-token-radius-seg-picker-xl); + --ax-public-segment-picker-border-radius-large: var(--ax-token-radius-seg-picker-l); + --ax-public-segment-picker-border-radius-medium: var(--ax-token-radius-seg-picker-m); + --ax-public-segment-picker-border-radius-small: var(--ax-token-radius-seg-picker-s); + --ax-public-segment-picker-border-radius-extra-small: var(--ax-token-radius-seg-picker-xs); + --ax-public-segment-picker-border-radius-xx-small: var(--ax-token-radius-seg-picker-xxs); + --ax-public-segment-picker-border-radius-xxx-small: var(--ax-token-radius-seg-picker-xxxs); +} + +@layer ui.component { + .extra-large { + border-radius: var(--ax-public-segment-picker-border-radius-extra-large); + } + + .large { + border-radius: var(--ax-public-segment-picker-border-radius-large); + } + + .medium { + border-radius: var(--ax-public-segment-picker-border-radius-medium); + } + + .small { + border-radius: var(--ax-public-segment-picker-border-radius-small); + } + + .extra-small { + border-radius: var(--ax-public-segment-picker-border-radius-extra-small); + } + + .xx-small { + border-radius: var(--ax-public-segment-picker-border-radius-xx-small); + } + + .xxx-small { + border-radius: var(--ax-public-segment-picker-border-radius-xxx-small); + } +} diff --git a/packages/ui/src/components/segment-picker/index.ts b/packages/ui/src/components/segment-picker/index.ts new file mode 100644 index 000000000..28188b14d --- /dev/null +++ b/packages/ui/src/components/segment-picker/index.ts @@ -0,0 +1 @@ +export * from './segment-picker'; diff --git a/packages/ui/src/components/segment-picker/item/segment-picker-item-shape.module.css b/packages/ui/src/components/segment-picker/item/segment-picker-item-shape.module.css new file mode 100644 index 000000000..354d2f77f --- /dev/null +++ b/packages/ui/src/components/segment-picker/item/segment-picker-item-shape.module.css @@ -0,0 +1,5 @@ +@layer ui.component { + .item:not(.circle) { + width: 100%; + } +} diff --git a/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx b/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx new file mode 100644 index 000000000..0cabf15d1 --- /dev/null +++ b/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx @@ -0,0 +1,68 @@ +import { + hasChildrenWithStringAndIcons, + hasIconChildrenOnly, + hasStringChildrenOnly, +} from '@ui/components/button/guards'; +import { NavButton } from '@ui/components/button/nav-button/nav-button'; +import { NavIconButtonProps } from '@ui/components/button/nav-button/nav-icon-button/nav-icon-button'; +import { NavIconLabelButtonProps } from '@ui/components/button/nav-button/nav-icon-label-button/nav-icon-label-button'; +import { NavLabelButtonProps } from '@ui/components/button/nav-button/nav-label-button/nav-label-button'; +import clsx from 'clsx'; +import { MouseEvent, useContext } from 'react'; + +import itemShapeStyles from './segment-picker-item-shape.module.css'; + +import { BaseButtonProps } from '../../button/types'; +import { SegmentPickerContext } from '../utils/context'; + +export type SegmentPickerItemProps = BaseButtonProps & { + value: string; +} & ( + | Pick + | Pick + | Pick + ); + +/** + * A single item in the SegmentPicker, rendered as a NavButton under the hood. + * + * Automatically receives size and shape from SegmentPicker context. + * Must be used only within a SegmentPicker component. + * + * Determines which NavButton variant to render based on its children + * (label only, icon only, or icon + label). + */ +export function Item({ children, value, ...buttonProps }: SegmentPickerItemProps) { + const context = useContext(SegmentPickerContext); + + if (!context) { + console.error('SegmentPicker.Item must be used within a SegmentPicker'); + return null; + } + + const { selectedValue, onSelect, shape, ...other } = context; + + const props = { + className: clsx(itemShapeStyles['item'], itemShapeStyles[shape ?? 'default']), + isSelected: selectedValue === value, + onClick: (event: MouseEvent) => onSelect(event, value), + shape, + children, + ...other, + ...buttonProps, + }; + + if (hasStringChildrenOnly(props)) { + return ; + } + + if (hasIconChildrenOnly(props)) { + return ; + } + + if (hasChildrenWithStringAndIcons(props)) { + return ; + } + + return null; +} diff --git a/packages/ui/src/components/segment-picker/segment-picker.module.css b/packages/ui/src/components/segment-picker/segment-picker.module.css new file mode 100644 index 000000000..b1daa7e8b --- /dev/null +++ b/packages/ui/src/components/segment-picker/segment-picker.module.css @@ -0,0 +1,25 @@ +:root { + --ax-public-segment-picker-gap: var(--ax-token-spacing-seg-picker-gap); + --ax-public-segment-picker-padding: var(--ax-token-spacing-seg-picker-pad); + --ax-public-segment-picker-background-color: var(--ax-ui-bg-tertiary-default); + --ax-public-segment-picker-circle-border-radius: var(--ax-token-radius-button-round); +} + +@layer ui.component { + .container { + display: flex; + gap: var(--ax-public-segment-picker-gap); + padding: var(--ax-public-segment-picker-padding); + + background-color: var(--ax-public-segment-picker-background-color); + + &.circle { + border-radius: var(--ax-public-segment-picker-circle-border-radius); + width: fit-content; + } + + &:not(.circle) { + width: 100%; + } + } +} diff --git a/packages/ui/src/components/segment-picker/segment-picker.tsx b/packages/ui/src/components/segment-picker/segment-picker.tsx new file mode 100644 index 000000000..34444346e --- /dev/null +++ b/packages/ui/src/components/segment-picker/segment-picker.tsx @@ -0,0 +1,79 @@ +import { Shape } from '@ui/components/button/types'; +import { Size } from '@ui/shared/types/size'; +import clsx from 'clsx'; +import { ForwardRefExoticComponent, MouseEvent, ReactElement, forwardRef, useState } from 'react'; + +import borderRadiusStyles from './border-radius-size.module.css'; +import styles from './segment-picker.module.css'; + +import { Item, SegmentPickerItemProps } from './item/segment-picker-item'; +import { SegmentPickerContext } from './utils/context'; +import { getValidShape } from './utils/get-valid-shape'; + +type SegmentPickerPropsBase = { + children: ReactElement[]; + size?: Size; + /** + * Controls the shape of the SegmentPicker and its items. + * (default) - Items stretch to fill the container equally. + * 'circle' - Items fit tightly around their content to maintain a circular shape. + * Only supported when items contain icons only. + */ + shape?: Shape; + className?: string; + onChange?: (event: MouseEvent, value: string) => void; +}; + +type ControlledSegmentPickerProps = { + /** The currently selected value (controlled mode). */ + value: string; + /** Must not be used in controlled mode. */ + defaultValue?: never; +} & SegmentPickerPropsBase; + +type UncontrolledSegmentPickerProps = { + /** The initial selected value (uncontrolled mode). */ + defaultValue: string; + /** Must not be used in uncontrolled mode. */ + value?: never; +} & SegmentPickerPropsBase; + +type SegmentPickerProps = ControlledSegmentPickerProps | UncontrolledSegmentPickerProps; + +type SegmentPickerComponent = ForwardRefExoticComponent> & { + Item: typeof Item; +}; + +export const SegmentPicker = forwardRef( + ({ children, value, defaultValue, size = 'medium', shape = 'default', className, onChange }, ref) => { + const validShape = getValidShape(shape, children); + const isControlled = value !== undefined; + const [internalValue, setInternalValue] = useState(defaultValue); + + const selectedValue = isControlled ? value : internalValue; + + const handleSelect = (event: MouseEvent, newValue: string) => { + if (!isControlled) { + setInternalValue(newValue); + } + onChange?.(event, newValue); + }; + + return ( + +
+ {children} +
+
+ ); + }, +) as SegmentPickerComponent; + +SegmentPicker.Item = Item; diff --git a/packages/ui/src/components/segment-picker/utils/context.ts b/packages/ui/src/components/segment-picker/utils/context.ts new file mode 100644 index 000000000..0a2a33938 --- /dev/null +++ b/packages/ui/src/components/segment-picker/utils/context.ts @@ -0,0 +1,12 @@ +import { Shape } from '@ui/components/button/types'; +import { Size } from '@ui/shared/types/size'; +import { MouseEvent, createContext } from 'react'; + +type SegmentPickerContextType = { + selectedValue: string | undefined; + onSelect: (event: MouseEvent, value: string) => void; + size?: Size; + shape?: Shape; +}; + +export const SegmentPickerContext = createContext(undefined); diff --git a/packages/ui/src/components/segment-picker/utils/get-valid-shape.spec.ts b/packages/ui/src/components/segment-picker/utils/get-valid-shape.spec.ts new file mode 100644 index 000000000..5ae8ea34b --- /dev/null +++ b/packages/ui/src/components/segment-picker/utils/get-valid-shape.spec.ts @@ -0,0 +1,40 @@ +import { type ReactElement, createElement } from 'react'; +import { vi } from 'vitest'; + +import { Item, type SegmentPickerItemProps } from '../item/segment-picker-item'; +import { getValidShape } from './get-valid-shape'; + +type PickerItem = ReactElement; + +const icon = createElement('svg'); + +// `SegmentPickerItemProps` requires `children` in props, so the guards read +// `item.props.children` — pass it as a prop rather than a createElement arg. +function iconItem(value: string): PickerItem { + // eslint-disable-next-line react/no-children-prop + return createElement(Item, { value, children: icon }) as PickerItem; +} + +function labelItem(value: string): PickerItem { + // eslint-disable-next-line react/no-children-prop + return createElement(Item, { value, children: 'label' }) as PickerItem; +} + +describe('getValidShape', () => { + it("returns 'default' unchanged without inspecting items", () => { + expect(getValidShape('default', [labelItem('a')])).toBe('default'); + }); + + it("returns 'circle' when every item has icon-only children", () => { + expect(getValidShape('circle', [iconItem('a'), iconItem('b')])).toBe('circle'); + }); + + it("falls back to 'default' and logs an error when an item has a string child", () => { + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + expect(getValidShape('circle', [iconItem('a'), labelItem('b')])).toBe('default'); + expect(spy).toHaveBeenCalledOnce(); + + spy.mockRestore(); + }); +}); diff --git a/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts b/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts new file mode 100644 index 000000000..d70248787 --- /dev/null +++ b/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts @@ -0,0 +1,22 @@ +import { Shape } from '@ui/components/button/types'; +import { ReactElement } from 'react'; + +import { hasIconChildrenOnly } from '../../button/guards'; +import { Item, SegmentPickerItemProps } from '../item/segment-picker-item'; + +export function getValidShape(shape: Shape, items: ReactElement[]): Shape { + if (shape !== 'circle') { + return shape; + } + + const everyItemHasOnlyIcon = items.every((item) => hasIconChildrenOnly({ children: item.props.children })); + + if (!everyItemHasOnlyIcon) { + console.error( + '[SegmentPicker] The "circle" shape can only be used when all SegmentPicker.Item components contain icon-only children.', + ); + return 'default'; + } + + return shape; +} diff --git a/packages/ui/src/components/select/index.ts b/packages/ui/src/components/select/index.ts new file mode 100644 index 000000000..1d873076d --- /dev/null +++ b/packages/ui/src/components/select/index.ts @@ -0,0 +1,2 @@ +export * from './select'; +export * from './types'; diff --git a/packages/ui/src/components/select/select-button/select-button.module.css b/packages/ui/src/components/select/select-button/select-button.module.css new file mode 100644 index 000000000..e54c8f3a2 --- /dev/null +++ b/packages/ui/src/components/select/select-button/select-button.module.css @@ -0,0 +1,60 @@ +:root { + --ax-public-select-border-size: 0.0625rem; + --ax-public-select-button-color: var(--ax-txt-primary-default); + --ax-public-select-button-border-color: var(--ax-input-stroke-primary-default); + --ax-public-select-button-border-color-focus: var(--ax-input-stroke-primary-focus); + --ax-public-select-button-color-disabled: var(--ax-input-stroke-primary-default); + + --ax-public-select-button-background-color-error: var(--ax-input-bg-primary-error); + --ax-public-select-button-border-color-error: var(--ax-input-stroke-primary-error); + --ax-public-select-button-color-error: var(--ax-txt-error-default); +} + +@layer ui.component { + .container { + display: flex; + width: 100%; + min-width: 100px; + align-items: center; + /* Push the caret to the far edge: Base UI's Select.Value wraps the value in + an inline span, so the inner container's width:100% resolves against that + span and cannot stretch - without this, the caret hugs the text. */ + justify-content: space-between; + background-color: transparent; + cursor: pointer; + + color: var(--ax-public-select-button-color); + + border: var(--ax-public-select-border-size) solid var(--ax-public-select-button-border-color); + + &:not(:hover) { + transition: all var(--ax-public-transition); + } + + &[data-popup-open] { + border-color: var(--ax-public-select-button-border-color-focus); + } + + &.container--error { + background-color: var(--ax-public-select-button-background-color-error); + border-color: var(--ax-public-select-button-border-color-error); + color: var(--ax-public-select-button-color-error); + } + + &:focus-visible { + border-color: var(--ax-public-select-button-border-color-focus); + outline: none; + } + + &:disabled { + color: var(--ax-public-select-button-color-disabled); + cursor: auto; + } + + svg { + width: 1rem; + height: 1rem; + min-width: 1rem; + } + } +} diff --git a/packages/ui/src/components/select/select-button/select-button.tsx b/packages/ui/src/components/select/select-button/select-button.tsx new file mode 100644 index 000000000..5c2d987b9 --- /dev/null +++ b/packages/ui/src/components/select/select-button/select-button.tsx @@ -0,0 +1,14 @@ +import { CaretDown } from '@phosphor-icons/react'; +import type { WithIcon } from '@ui/shared/types/with-icon'; +import { type ComponentPropsWithoutRef, forwardRef } from 'react'; + +export const SelectButton = forwardRef & WithIcon>( + function SelectButton({ icon, children, ...rest }, ref) { + return ( + + ); + }, +); diff --git a/packages/ui/src/components/select/select-option/select-option.tsx b/packages/ui/src/components/select/select-option/select-option.tsx new file mode 100644 index 000000000..d59c6f931 --- /dev/null +++ b/packages/ui/src/components/select/select-option/select-option.tsx @@ -0,0 +1,26 @@ +import { Select as SelectBase } from '@base-ui/react/select'; +import type { ItemSize } from '@ui/shared/types/item-size'; +import clsx from 'clsx'; + +import listItemSize from '@ui/shared/styles/list-item-size.module.css'; +import listItemStyles from '@ui/shared/styles/list-item.module.css'; + +import type { SelectItem } from '../types'; + +type SelectOptionProps = SelectItem & { + size?: ItemSize; +}; + +export function SelectOption({ icon, value, label, size = 'medium', disabled }: SelectOptionProps) { + return ( + + {icon} + {label} + + ); +} diff --git a/packages/ui/src/components/select/select-value/select-value.module.css b/packages/ui/src/components/select/select-value/select-value.module.css new file mode 100644 index 000000000..9d6f4f261 --- /dev/null +++ b/packages/ui/src/components/select/select-value/select-value.module.css @@ -0,0 +1,20 @@ +@layer ui.component { + .container { + width: 100%; + display: flex; + gap: 0.5rem; + overflow: hidden; + + .placeholder { + opacity: 0.5; + } + + span { + width: 100%; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } +} diff --git a/packages/ui/src/components/select/select-value/select-value.tsx b/packages/ui/src/components/select/select-value/select-value.tsx new file mode 100644 index 000000000..e8954ca24 --- /dev/null +++ b/packages/ui/src/components/select/select-value/select-value.tsx @@ -0,0 +1,29 @@ +import clsx from 'clsx'; + +import style from './select-value.module.css'; + +import type { SelectItem } from '../types'; + +type SelectValueProps = { + value: string | number | null; + items: SelectItem[]; + placeholder?: string; +}; + +export function SelectValue({ value, items, placeholder }: SelectValueProps) { + const selectedOption = items.find((item) => item.value === value); + const label = selectedOption?.label ?? placeholder; + + return ( +
+ {selectedOption?.icon} + + {label} + +
+ ); +} diff --git a/packages/ui/src/components/select/select.module.css b/packages/ui/src/components/select/select.module.css new file mode 100644 index 000000000..e0cd26370 --- /dev/null +++ b/packages/ui/src/components/select/select.module.css @@ -0,0 +1,11 @@ +@layer ui.component { + .container { + display: grid; + position: relative; + width: 100%; + } + + .popup { + width: var(--anchor-width); + } +} diff --git a/packages/ui/src/components/select/select.tsx b/packages/ui/src/components/select/select.tsx new file mode 100644 index 000000000..2d6116fdb --- /dev/null +++ b/packages/ui/src/components/select/select.tsx @@ -0,0 +1,128 @@ +import { Select as SelectBase } from '@base-ui/react/select'; +import clsx from 'clsx'; +import type { SyntheticEvent } from 'react'; + +import selectButtonStyles from './select-button/select-button.module.css'; +import style from './select.module.css'; +import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; +import inputSizeStyles from '@ui/shared/styles/input-size.module.css'; +import listBoxStyles from '@ui/shared/styles/list-box.module.css'; + +import type { ItemSize } from '../../shared/types/item-size'; +import { Separator } from '../separator/separator'; +import { SelectButton } from './select-button/select-button'; +import { SelectOption } from './select-option/select-option'; +import { SelectValue } from './select-value/select-value'; +import type { SelectItem } from './types'; + +type SelectValueType = string | number | null; + +export type SelectBaseProps = { + /** + * Custom class name for the component. + */ + className?: string; + /** + * Size of the select input + */ + size?: ItemSize; + /** + * Placeholder text for the select input + */ + placeholder?: string; + /** + * List of items to display in the select dropdown + */ + items: SelectItem[]; + /** + * Whether the select has an error + */ + error?: boolean; + /** + * The controlled value of the select. + */ + value?: SelectValueType; + /** + * The default value of the select when uncontrolled. + */ + defaultValue?: SelectValueType; + /** + * Callback fired when the value of the select changes. + */ + onChange?: (event: SyntheticEvent | Event | null, value: SelectValueType) => void; + /** + * Whether the select is disabled. + */ + disabled?: boolean; + /** + * Identifies the field when a form is submitted. + */ + name?: string; + /** + * Whether the user must choose a value before submitting a form. + */ + required?: boolean; +}; + +/** + * Component for displaying a select dropdown with customizable size, placeholder, and item list + */ +export function Select({ + className, + size = 'medium', + items, + placeholder, + error = false, + value, + defaultValue, + onChange, + disabled, + name, + required, +}: SelectBaseProps) { + const triggerClassName = clsx( + selectButtonStyles['container'], + { + [selectButtonStyles['container--error']]: error, + }, + inputFontStyles[size], + inputSizeStyles[size], + className, + ); + + return ( +
+ { + onChange?.(eventDetails.event ?? null, nextValue as SelectValueType); + }} + > + }> + + {(currentValue) => ( + + )} + + + + + + {items.map((item, index) => + item.type === 'separator' ? ( + + ) : ( + + ), + )} + + + + +
+ ); +} diff --git a/packages/ui/src/components/select/types.ts b/packages/ui/src/components/select/types.ts new file mode 100644 index 000000000..9ab344f43 --- /dev/null +++ b/packages/ui/src/components/select/types.ts @@ -0,0 +1,6 @@ +import { ListItem } from '../../shared/types/list-item'; + +export type SelectItem = ListItem & { + value?: string | number | null; + label?: string; +}; diff --git a/packages/ui/src/components/separator/index.ts b/packages/ui/src/components/separator/index.ts new file mode 100644 index 000000000..68fc331ab --- /dev/null +++ b/packages/ui/src/components/separator/index.ts @@ -0,0 +1 @@ +export * from './separator'; diff --git a/packages/ui/src/components/separator/separator.module.css b/packages/ui/src/components/separator/separator.module.css new file mode 100644 index 000000000..c2535905e --- /dev/null +++ b/packages/ui/src/components/separator/separator.module.css @@ -0,0 +1,16 @@ +:root { + --ax-public-list-item-separator-border-size: 0.0625rem; + --ax-public-list-item-separator-background-color: var(--ax-ui-separator-primary-default); +} + +@layer ui.component { + .separator { + border-top: var(--ax-public-list-item-separator-border-size) solid + var(--ax-public-list-item-separator-background-color); + width: 100%; + margin: 0; + border-bottom: none; + border-left: none; + border-right: none; + } +} diff --git a/packages/ui/src/components/separator/separator.tsx b/packages/ui/src/components/separator/separator.tsx new file mode 100644 index 000000000..20022e5d5 --- /dev/null +++ b/packages/ui/src/components/separator/separator.tsx @@ -0,0 +1,8 @@ +import styles from './separator.module.css'; + +/** + * A visual separator component that creates a horizontal line to divide content. + */ +export function Separator() { + return
; +} diff --git a/packages/ui/src/components/snackbar/components/action-buttons.module.css b/packages/ui/src/components/snackbar/components/action-buttons.module.css new file mode 100644 index 000000000..4e32c0171 --- /dev/null +++ b/packages/ui/src/components/snackbar/components/action-buttons.module.css @@ -0,0 +1,8 @@ +@layer ui.component { + .container { + display: flex; + align-items: center; + gap: 0.5rem; + margin-left: auto; + } +} diff --git a/packages/ui/src/components/snackbar/components/action-buttons.tsx b/packages/ui/src/components/snackbar/components/action-buttons.tsx new file mode 100644 index 000000000..ef4d02067 --- /dev/null +++ b/packages/ui/src/components/snackbar/components/action-buttons.tsx @@ -0,0 +1,43 @@ +import { X } from '@phosphor-icons/react'; +import { NavButton } from '@ui/components/button/nav-button/nav-button'; +import { Variant } from '@ui/components/button/regular-button/types'; + +import styles from './action-buttons.module.css'; + +import { Button } from '../../button/regular-button/button'; +import { SnackbarType } from '../types'; + +type ActionButtonsProps = { + variant: string; + buttonLabel?: string; + onButtonClick?: () => void; + close: boolean; + onClose?: () => void; +}; + +export function ActionButtons({ variant, buttonLabel, onButtonClick, close, onClose }: ActionButtonsProps) { + const buttonTypeMap: Record = { + [SnackbarType.DEFAULT]: 'primary', + [SnackbarType.ERROR]: 'error', + [SnackbarType.INFO]: 'primary', + [SnackbarType.WARNING]: 'warning', + [SnackbarType.SUCCESS]: 'success', + }; + + const buttonType = buttonTypeMap[variant] || 'primary'; + + return ( +
+ {buttonLabel && onButtonClick && ( + + )} + {close && onClose && ( + + + + )} +
+ ); +} diff --git a/packages/ui/src/components/snackbar/components/icon.module.css b/packages/ui/src/components/snackbar/components/icon.module.css new file mode 100644 index 000000000..d969ca01e --- /dev/null +++ b/packages/ui/src/components/snackbar/components/icon.module.css @@ -0,0 +1,33 @@ +@layer ui.component { + .container { + display: flex; + padding-top: 0.125rem; + + &.center { + align-self: center; + padding-top: 0; + } + + .status { + width: 1.375rem; + height: 1.375rem; + color: var(--ax-public-snackbar-default-icon-color); + + &.success { + color: var(--ax-public-snackbar-success-icon-color); + } + + &.error { + color: var(--ax-public-snackbar-error-icon-color); + } + + &.warning { + color: var(--ax-public-snackbar-warning-icon-color); + } + + &.info { + color: var(--ax-public-snackbar-info-icon-color); + } + } + } +} diff --git a/packages/ui/src/components/snackbar/components/icon.tsx b/packages/ui/src/components/snackbar/components/icon.tsx new file mode 100644 index 000000000..529842bea --- /dev/null +++ b/packages/ui/src/components/snackbar/components/icon.tsx @@ -0,0 +1,24 @@ +import { Info } from '@phosphor-icons/react'; +import { WithIcon } from '@ui/shared/types/with-icon'; +import { clsx } from 'clsx'; + +import styles from './icon.module.css'; + +import { SnackbarVariant } from '../types'; + +type IconProps = WithIcon & { + isCentered: boolean; + variant: SnackbarVariant; +}; + +export function Icon({ icon, isCentered, variant }: IconProps) { + return ( +
+ {icon ?? } +
+ ); +} diff --git a/packages/ui/src/components/snackbar/components/message.module.css b/packages/ui/src/components/snackbar/components/message.module.css new file mode 100644 index 000000000..2df188f49 --- /dev/null +++ b/packages/ui/src/components/snackbar/components/message.module.css @@ -0,0 +1,18 @@ +@layer ui.component { + .container { + display: flex; + flex-direction: column; + text-align: left; + flex: 1; + min-width: 0; + word-break: break-word; + } + + .title { + color: var(--ax-public-snackbar-title-color); + } + + .subtitle { + color: var(--ax-public-snackbar-subtitle-color); + } +} diff --git a/packages/ui/src/components/snackbar/components/message.tsx b/packages/ui/src/components/snackbar/components/message.tsx new file mode 100644 index 000000000..d1de68dff --- /dev/null +++ b/packages/ui/src/components/snackbar/components/message.tsx @@ -0,0 +1,17 @@ +import clsx from 'clsx'; + +import styles from './message.module.css'; + +type MessageProps = { + title: string; + subtitle: string | undefined; +}; + +export function Message({ title, subtitle }: MessageProps) { + return ( +
+ {title} + {subtitle && {subtitle}} +
+ ); +} diff --git a/packages/ui/src/components/snackbar/index.ts b/packages/ui/src/components/snackbar/index.ts new file mode 100644 index 000000000..0a77cc2b9 --- /dev/null +++ b/packages/ui/src/components/snackbar/index.ts @@ -0,0 +1,2 @@ +export * from './snackbar'; +export * from './types'; diff --git a/packages/ui/src/components/snackbar/snackbar.module.css b/packages/ui/src/components/snackbar/snackbar.module.css new file mode 100644 index 000000000..01d554431 --- /dev/null +++ b/packages/ui/src/components/snackbar/snackbar.module.css @@ -0,0 +1,81 @@ +:root { + --ax-public-snackbar-border-size: 0.0625rem; + --ax-public-snackbar-padding: var(--ax-token-spacing-snackbar-v-pad-1) var(--ax-token-spacing-snackbar-h-pad-1); + --ax-public-snackbar-border-radius: var(--ax-token-radius-snackbar); + --ax-public-snackbar-content-gap: var(--ax-token-spacing-snackbar-gap-1); + + --ax-public-snackbar-width: 31.25rem; + --ax-public-snackbar-title-color: var(--ax-txt-primary-default); + --ax-public-snackbar-subtitle-color: var(--ax-txt-secondary-default); + --ax-public-snackbar-close-icon-color: var(--ax-txt-tertiary-default); + + --ax-public-snackbar-default-border: var(--ax-ui-stroke-secondary-default); + --ax-public-snackbar-default-background: var(--ax-ui-bg-primary-default); + --ax-public-snackbar-default-icon-color: var(--ax-txt-tertiary-default); + + --ax-public-snackbar-success-border: var(--ax-colors-green-300); /* missing token */ + --ax-public-snackbar-success-background: + linear-gradient(0deg, var(--ax-snackbar-bg-success) 0%, var(--ax-snackbar-bg-success) 100%), + var(--ax-ui-bg-primary-default); + --ax-public-snackbar-success-icon-color: var(--ax-colors-green-300); /* missing token */ + + --ax-public-snackbar-error-border: var(--ax-colors-red-400); /* missing token */ + --ax-public-snackbar-error-background: + linear-gradient(0deg, var(--ax-snackbar-bg-error) 0%, var(--ax-snackbar-bg-error) 100%), + var(--ax-ui-bg-primary-default); + --ax-public-snackbar-error-icon-color: var(--ax-colors-red-400); /* missing token */ + + --ax-public-snackbar-warning-border: var(--ax-colors-orange-300); /* missing token */ + --ax-public-snackbar-warning-background: + linear-gradient(0deg, var(--ax-snackbar-bg-warning) 0%, var(--ax-snackbar-bg-warning) 100%), + var(--ax-ui-bg-primary-default); + + --ax-public-snackbar-warning-icon-color: var(--ax-colors-orange-300); /* missing token */ + + --ax-public-snackbar-info-border: var(--ax-ui-bg-tertiary-selected); + --ax-public-snackbar-info-background: + linear-gradient(0deg, var(--ax-snackbar-bg-information) 0%, var(--ax-snackbar-bg-information) 100%), + var(--ax-ui-bg-primary-default); + --ax-public-snackbar-info-icon-color: var(--ax-colors-blue-400); +} + +@layer ui.component { + .container { + display: flex; + width: calc( + var(--ax-public-snackbar-width) + 2 * var(--ax-token-spacing-snackbar-h-pad-1) + 2 * + var(--ax-public-snackbar-border-size) + ); + border: var(--ax-public-snackbar-border-size) solid var(--ax-public-snackbar-default-border); + border-radius: var(--ax-public-snackbar-border-radius); + background: var(--ax-public-snackbar-default-background); + padding: var(--ax-public-snackbar-padding); + + .content { + display: flex; + gap: var(--ax-public-snackbar-content-gap); + width: 100%; + min-width: 0; + } + + &.success { + background: var(--ax-public-snackbar-success-background); + border-color: var(--ax-public-snackbar-success-border); + } + + &.error { + background: var(--ax-public-snackbar-error-background); + border-color: var(--ax-public-snackbar-error-border); + } + + &.warning { + background: var(--ax-public-snackbar-warning-background); + border-color: var(--ax-public-snackbar-warning-border); + } + + &.info { + background: var(--ax-public-snackbar-info-background); + border-color: var(--ax-public-snackbar-info-border); + } + } +} diff --git a/packages/ui/src/components/snackbar/snackbar.tsx b/packages/ui/src/components/snackbar/snackbar.tsx new file mode 100644 index 000000000..1b844f3cc --- /dev/null +++ b/packages/ui/src/components/snackbar/snackbar.tsx @@ -0,0 +1,64 @@ +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import styles from './snackbar.module.css'; + +import { ActionButtons } from './components/action-buttons'; +import { Icon } from './components/icon'; +import { Message } from './components/message'; +import { SnackbarVariant } from './types'; + +export type SnackbarProps = { + /** + * Visual style variant of the snackbar + */ + variant: SnackbarVariant; + /** + * Main message displayed in the snackbar + */ + title: string; + /** + * Optional secondary message displayed below the title + */ + subtitle?: string; + /** + * Label for the action button + */ + buttonLabel?: string; + /** + * Callback fired when the action button is clicked + */ + onButtonClick?: () => void; + /** + * Whether to show the close button + */ + close?: boolean; + /** + * Callback fired when the snackbar is closed + */ + onClose?: () => void; +}; + +/** + * A Snackbar component that displays brief messages about app processes. + * Purely presentational: it does not position or auto-dismiss itself - placement + * and lifetime (e.g. an auto-hide timer) are the consumer's responsibility, and + * dismissal is driven through `close` / `onClose`. + */ +export const Snackbar = forwardRef( + ({ variant, title, subtitle, buttonLabel, onButtonClick, close = false, onClose }, ref) => ( +
+
+ + + +
+
+ ), +); diff --git a/packages/ui/src/components/snackbar/types.ts b/packages/ui/src/components/snackbar/types.ts new file mode 100644 index 000000000..1951ad96a --- /dev/null +++ b/packages/ui/src/components/snackbar/types.ts @@ -0,0 +1,9 @@ +export enum SnackbarType { + SUCCESS = 'success', + ERROR = 'error', + WARNING = 'warning', + INFO = 'info', + DEFAULT = 'default', +} + +export type SnackbarVariant = `${SnackbarType}`; diff --git a/packages/ui/src/components/status/index.ts b/packages/ui/src/components/status/index.ts new file mode 100644 index 000000000..420cc02aa --- /dev/null +++ b/packages/ui/src/components/status/index.ts @@ -0,0 +1 @@ +export * from './status'; diff --git a/packages/ui/src/components/status/status.module.css b/packages/ui/src/components/status/status.module.css new file mode 100644 index 000000000..d3e373b49 --- /dev/null +++ b/packages/ui/src/components/status/status.module.css @@ -0,0 +1,33 @@ +:root { + --ax-public-status-right: var(--ax-primitive-even-12) /* missing token */; + --ax-public-status-size: var(--ax-primitive-even-20) /* missing token */; + --ax-public-status-icon-size: var(--ax-primitive-even-12) /* missing token */; + --ax-public-status-invalid-color: var(--ax-colors-orange-100) /* missing token */; + --ax-public-status-invalid-background-color: var(--ax-colors-orange-400) /* missing token */; +} + +@layer ui.component { + .status-container { + display: inline-flex; + align-items: center; + justify-content: center; + position: absolute; + right: var(--ax-public-status-right); + top: 0; + transform: translateY(-50%); + height: var(--ax-public-status-size); + aspect-ratio: 1 / 1; + border-radius: 50%; + + svg { + height: var(--ax-public-status-icon-size); + aspect-ratio: 1 / 1; + fill: currentColor; + } + + &.status-container--invalid { + color: var(--ax-public-status-invalid-color); + background: var(--ax-public-status-invalid-background-color); + } + } +} diff --git a/packages/ui/src/components/status/status.tsx b/packages/ui/src/components/status/status.tsx new file mode 100644 index 000000000..a3d690afc --- /dev/null +++ b/packages/ui/src/components/status/status.tsx @@ -0,0 +1,32 @@ +import { ExclamationMark } from '@phosphor-icons/react'; +import clsx from 'clsx'; + +import styles from './status.module.css'; + +export type ValidationStatus = 'invalid'; + +type Props = { + /** + * The validation status to display. + */ + status?: ValidationStatus; + /** + * Custom class name for the component. + */ + className?: string; +}; + +/** + * A component that displays a visual indicator based on validation status. + */ +export function Status({ status, className }: Props) { + if (status === 'invalid') { + return ( + + + + ); + } + + return null; +} diff --git a/packages/ui/src/components/switch/icon-switch/icon-switch.module.css b/packages/ui/src/components/switch/icon-switch/icon-switch.module.css new file mode 100644 index 000000000..a45808451 --- /dev/null +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.module.css @@ -0,0 +1,106 @@ +:root { + --ax-public-icon-switch-width: 4.125rem; + --ax-public-icon-switch-height: 2.125rem; + --ax-public-icon-switch-thumb-size: 1.75rem; + --ax-public-icon-switch-thumb-offset: 0.1875rem; + --ax-public-icon-switch-translate: 2rem; + --ax-public-icon-switch-padding: 0.5rem; + --ax-public-icon-switch-icon-size: 1rem; + + --ax-public-icon-switch-track-bg: var(--ax-ui-bg-tertiary-default); + --ax-public-icon-switch-thumb-bg-primary: var(--ax-ui-bg-tertiary-selected); + --ax-public-icon-switch-icon-color: var(--ax-txt-tertiary-default); + --ax-public-icon-switch-icon-selected-color: var(--ax-txt-primary-white); + --ax-public-icon-switch-thumb-bg-secondary: var(--ax-ui-bg-primary-default); +} + +@layer ui.component { + .container { + width: var(--ax-public-icon-switch-width); + height: var(--ax-public-icon-switch-height); + position: relative; + border: none; + } + + .track { + position: absolute; + inset: 0; + border-radius: calc(var(--ax-public-icon-switch-height) / 2); + background-color: var(--ax-public-icon-switch-track-bg); + transition: background-color 0.1s ease-in-out; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 var(--ax-public-icon-switch-padding); + color: var(--ax-public-icon-switch-icon-color); + } + + .track > div { + width: var(--ax-public-icon-switch-icon-size); + height: var(--ax-public-icon-switch-icon-size); + display: flex; + align-items: center; + justify-content: center; + } + + .thumb { + position: absolute; + top: var(--ax-public-icon-switch-thumb-offset); + left: var(--ax-public-icon-switch-thumb-offset); + width: var(--ax-public-icon-switch-thumb-size); + height: var(--ax-public-icon-switch-thumb-size); + border-radius: 50%; + background-color: var(--ax-public-icon-switch-thumb-bg); + transition: transform 0.1s ease-in-out; + display: flex; + align-items: center; + justify-content: center; + + &.primary { + background-color: var(--ax-public-icon-switch-thumb-bg-primary); + color: var(--ax-public-icon-switch-icon-selected-color); + } + + &.secondary { + background-color: var(--ax-public-icon-switch-thumb-bg-secondary); + color: var(--ax-public-icon-switch-thumb-bg-primary); + } + } + + .icon { + width: var(--ax-public-icon-switch-icon-size); + height: var(--ax-public-icon-switch-icon-size); + display: flex; + align-items: center; + justify-content: center; + } + + .thumb .icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + .thumb .icon.icon-right { + transform: translate(-50%, -50%) rotate(90deg); + } + + .container[data-checked] .thumb { + transform: translateX(var(--ax-public-icon-switch-translate)); + } + + /* Swap the thumb icon off the switch state (works controlled + uncontrolled). + Both icons are absolutely centered in the thumb; only one is shown. */ + .thumb .icon-checked { + display: none; + } + + .container[data-checked] .thumb .icon-unchecked { + display: none; + } + + .container[data-checked] .thumb .icon-checked { + display: flex; + } +} diff --git a/packages/ui/src/components/switch/icon-switch/icon-switch.tsx b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx new file mode 100644 index 000000000..22a658718 --- /dev/null +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx @@ -0,0 +1,49 @@ +import { WithIcon } from '@ui/shared/types/with-icon'; +import clsx from 'clsx'; +import { ReactNode } from 'react'; + +import styles from './icon-switch.module.css'; + +import { BaseSwitchProps, Switch } from '../switch'; + +export type IconSwitchProps = WithIcon & { + IconChecked: ReactNode; + variant?: Variant; +} & BaseSwitchProps; + +type Variant = 'primary' | 'secondary'; + +export function IconSwitch({ + icon, + IconChecked, + className, + checked, + variant = 'primary', + onChange, + ...props +}: IconSwitchProps) { + return ( + +
{icon}
+
{IconChecked}
+ + } + thumbChildren={ +
+ {/* Render both icons and let CSS swap them off the switch's + `data-checked` state, so the thumb icon tracks the real state in + uncontrolled mode too (reading the `checked` prop only works when + the switch is controlled). */} +
{icon}
+
{IconChecked}
+
+ } + {...props} + /> + ); +} diff --git a/packages/ui/src/components/switch/index.ts b/packages/ui/src/components/switch/index.ts new file mode 100644 index 000000000..0f681b4e2 --- /dev/null +++ b/packages/ui/src/components/switch/index.ts @@ -0,0 +1,2 @@ +export * from './switch'; +export * from './icon-switch/icon-switch'; diff --git a/packages/ui/src/components/switch/switch-size.module.css b/packages/ui/src/components/switch/switch-size.module.css new file mode 100644 index 000000000..1305dda2f --- /dev/null +++ b/packages/ui/src/components/switch/switch-size.module.css @@ -0,0 +1,97 @@ +/* public variables */ +:root { + --ax-public-switch-width-medium: 1.875rem; + --ax-public-switch-height-medium: 1.25rem; + --ax-public-switch-thumb-padding-medium: 0.1875rem; + + --ax-public-switch-width-small: 1.75rem; + --ax-public-switch-height-small: 1.125rem; + --ax-public-switch-thumb-padding-small: 0.1875rem; + + --ax-public-switch-width-extra-small: 1.625rem; + --ax-public-switch-height-extra-small: 1rem; + --ax-public-switch-thumb-padding-extra-small: 0.1875rem; +} + +/* private calculations */ +:root { + --ax-switch-thumb-size-medium: calc( + var(--ax-public-switch-height-medium) - 2 * var(--ax-public-switch-thumb-padding-medium) + ); + + --ax-switch-thumb-size-small: calc( + var(--ax-public-switch-height-small) - 2 * var(--ax-public-switch-thumb-padding-small) + ); + + --ax-switch-thumb-size-extra-small: calc( + var(--ax-public-switch-height-extra-small) - 2 * var(--ax-public-switch-thumb-padding-extra-small) + ); +} + +@layer ui.base { + .medium { + width: var(--ax-public-switch-width-medium); + height: var(--ax-public-switch-height-medium); + + & .thumb { + width: var(--ax-switch-thumb-size-medium); + height: var(--ax-switch-thumb-size-medium); + left: var(--ax-public-switch-thumb-padding-medium); + } + + &[data-checked] { + .thumb { + transform: translateX( + calc( + var(--ax-public-switch-width-medium) - var(--ax-switch-thumb-size-medium) - 2 * + var(--ax-public-switch-thumb-padding-medium) - var(--ax-public-switch-border-size) + ) + ); + } + } + } + + .small { + width: var(--ax-public-switch-width-small); + height: var(--ax-public-switch-height-small); + + .thumb { + width: var(--ax-switch-thumb-size-small); + height: var(--ax-switch-thumb-size-small); + left: var(--ax-public-switch-thumb-padding-small); + } + + &[data-checked] { + .thumb { + transform: translateX( + calc( + var(--ax-public-switch-width-small) - var(--ax-switch-thumb-size-small) - 2 * + var(--ax-public-switch-thumb-padding-small) - var(--ax-public-switch-border-size) + ) + ); + } + } + } + + .extra-small { + width: var(--ax-public-switch-width-extra-small); + height: var(--ax-public-switch-height-extra-small); + + .thumb { + width: var(--ax-switch-thumb-size-extra-small); + height: var(--ax-switch-thumb-size-extra-small); + left: var(--ax-public-switch-thumb-padding-extra-small); + } + + &[data-checked] { + .thumb { + transform: translateX( + calc( + var(--ax-public-switch-width-extra-small) - var(--ax-switch-thumb-size-extra-small) - 2 * + var(--ax-public-switch-thumb-padding-extra-small) - var(--ax-public-switch-border-size) + ) + ); + } + } + } +} diff --git a/packages/ui/src/components/switch/switch.module.css b/packages/ui/src/components/switch/switch.module.css new file mode 100644 index 000000000..6d7451963 --- /dev/null +++ b/packages/ui/src/components/switch/switch.module.css @@ -0,0 +1,78 @@ +@import './switch-size.module.css'; + +:root { + --ax-public-switch-outline-color: var(--ax-ui-stroke-secondary-default); + --ax-public-switch-disabled-background-color: var(--ax-ui-bg-tertiary-default); + --ax-public-switch-disabled-border-color: var(--ax-ui-stroke-secondary-default); + --ax-public-track-no-selected-color: var(--ax-ui-bg-primary-default); + --ax-public-track-selected-color: var(--ax-ui-bg-tertiary-selected); + --ax-public-thumb-no-selected-color: var(--ax-nav-button-icon-primary-default); + --ax-public-thumb-disabled-color: var(--ax-nav-button-icon-primary-disabled); + --ax-public-thumb-selected-color: var(--ax-colors-gray-100,); /* missing token */ + + --ax-public-switch-focus-border-color: var(--ax-txt-primary-default); + --ax-public-switch-border-size: 0.0625rem; +} + +@layer ui.base { + .container { + display: inline-flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + position: relative; + cursor: pointer; + border-radius: 100px; + border: var(--ax-public-switch-border-size) solid var(--ax-public-switch-outline-color); + + &:not(:hover) { + transition: background-color var(--ax-public-transition); + } + + .thumb { + position: absolute; + border-radius: 100px; + background-color: var(--ax-public-thumb-no-selected-color); + transition: transform var(--ax-public-transition); + } + + .track { + position: absolute; + width: 100%; + height: 100%; + border-radius: 100px; + } + + .thumb-contents { + display: contents; + } + + &:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--ax-public-switch-focus-border-color); + } + + &[data-checked] { + .thumb { + background-color: var(--ax-public-thumb-selected-color); + } + + .track { + background-color: var(--ax-public-track-selected-color); + } + } + + &[data-disabled] { + cursor: auto; + border-color: var(--ax-public-switch-disabled-border-color); + + .track { + background-color: var(--ax-public-switch-disabled-background-color); + } + + .thumb { + background-color: var(--ax-public-thumb-disabled-color); + } + } + } +} diff --git a/packages/ui/src/components/switch/switch.tsx b/packages/ui/src/components/switch/switch.tsx new file mode 100644 index 000000000..a23c9ac32 --- /dev/null +++ b/packages/ui/src/components/switch/switch.tsx @@ -0,0 +1,82 @@ +import { Switch as SwitchBase } from '@base-ui/react/switch'; +import { SelectorSize } from '@ui/shared/types/selector-size'; +import clsx from 'clsx'; + +import switchStyles from './switch.module.css'; + +type SwitchRootProps = Omit< + React.ComponentProps, + 'onCheckedChange' | 'render' | 'children' | 'className' +>; + +export type BaseSwitchProps = { + /** + * Size of the switch component + */ + size?: SelectorSize; + /** + * Custom content for the thumb of the switch + */ + thumbChildren?: React.ReactNode; + /** + * Custom content for the track of the switch + */ + trackChildren?: React.ReactNode; + /** + * Custom class name for the switch component + */ + className?: string; + /** + * Whether the switch is checked or not + */ + checked?: boolean; + /** + * Whether the switch is disabled + */ + disabled?: boolean; + /** + * Callback function when the switch state changes + */ + onChange?: (checked: boolean, event: Event) => void; +} & SwitchRootProps; + +/** + * A Switch component that allows users to toggle between two states, such as on and off. + * Typically used for settings or preferences, it provides immediate visual feedback. + */ +export function Switch({ + size = 'medium', + className, + thumbChildren, + trackChildren, + onChange, + ...props +}: BaseSwitchProps) { + function handleCheckedChange(checked: boolean, eventDetails: { event: Event }) { + onChange?.(checked, eventDetails.event); + } + + return ( + } + {...props} + > + {trackChildren ?? } + ( + + ) + : undefined + } + > + {thumbChildren} + + + ); +} diff --git a/packages/ui/src/components/text-area/index.ts b/packages/ui/src/components/text-area/index.ts new file mode 100644 index 000000000..e18b3258a --- /dev/null +++ b/packages/ui/src/components/text-area/index.ts @@ -0,0 +1 @@ +export * from './text-area'; diff --git a/packages/ui/src/components/text-area/text-area.module.css b/packages/ui/src/components/text-area/text-area.module.css new file mode 100644 index 000000000..1334872aa --- /dev/null +++ b/packages/ui/src/components/text-area/text-area.module.css @@ -0,0 +1,64 @@ +:root { + --ax-public-textarea-color-disabled: var(--ax-txt-quaternary-default); + --ax-public-textarea-root-color: var(--ax-txt-secondary-default); + --ax-public-textarea-root-border-color: var(--ax-input-stroke-primary-default); + --ax-public-textarea-root-border-color-focus: var(--ax-input-stroke-primary-focus); + --ax-public-textarea-root-border-size: 0.0625rem; + --ax-public-textarea-root-background-color-error: var(--ax-input-bg-primary-error); + --ax-public-textarea-root-border-color-error: var(--ax-input-stroke-primary-error); + --ax-public-textarea-root-color-error: var(--ax-txt-error-default); + --ax-public-textarea-root-color-disabled: var(--ax-txt-quaternary-default); +} + +@layer ui.component { + .text-area-container { + display: flex; + min-width: 100px; + align-items: flex-start; + gap: 0.5rem; + background-color: transparent; + color: var(--ax-public-textarea-root-color); + border: var(--ax-public-textarea-root-border-size) solid var(--ax-public-textarea-root-border-color); + + &:not(:hover) { + transition: all var(--ax-public-transition); + } + + &:has(.text-area:focus-visible) { + border-color: var(--ax-public-textarea-root-border-color-focus); + } + + &:global(.base--error) { + background-color: var(--ax-public-textarea-root-background-color-error); + border-color: var(--ax-public-textarea-root-border-color-error); + color: var(--ax-public-textarea-root-color-error); + } + + &:global(.base--disabled) { + color: var(--ax-public-textarea-root-color-disabled); + background-color: var(--ax-public-textarea-root-background-color-disabled); + cursor: not-allowed; + } + } + + .text-area { + border: none; + background: transparent; + padding: 0; + color: unset; + width: 100%; + resize: none; + /* Not the `font` shorthand - it would also set the size, overriding the + ax-public-* typography class the component is given. */ + font-family: inherit; + + &:focus-visible { + outline: none; + } + + &:disabled { + color: var(--ax-public-textarea-color-disabled); + cursor: not-allowed; + } + } +} diff --git a/packages/ui/src/components/text-area/text-area.tsx b/packages/ui/src/components/text-area/text-area.tsx new file mode 100644 index 000000000..57b2282b7 --- /dev/null +++ b/packages/ui/src/components/text-area/text-area.tsx @@ -0,0 +1,124 @@ +import clsx from 'clsx'; +import type React from 'react'; +import TextareaAutosize from 'react-textarea-autosize'; + +import styles from './text-area.module.css'; +import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; +import inputSizeStyles from '@ui/shared/styles/input-size.module.css'; + +import type { ItemSize } from '../../shared/types/item-size'; + +export type TextAreaProps = { + /** + * Controlled value of the textarea + */ + value?: string; + /** + * Initial value of the textarea + */ + defaultValue?: string; + /** + * Placeholder text for the textarea + */ + placeholder?: string; + /** + * Size of the textarea + */ + size?: ItemSize; + /** + * Maximum number of rows the textarea can expand to + */ + maxRows?: number; + /** + * Minimum number of rows the textarea can expand to + */ + minRows?: number; + /** + * Whether the textarea is disabled + */ + disabled?: boolean; + /** + * Whether the textarea has an error + */ + error?: boolean; + /** + * Callback function to handle change in textarea value + */ + onChange?: (event: React.ChangeEvent) => void; + /** + * Callback function to handle click + */ + onClick?: (event: React.MouseEvent) => void; + /** + * Function called when the input loses focus. + * The event parameter may be undefined. + */ + onBlur?: (event?: React.FocusEvent) => void; + /** + * Custom class name for the textarea + */ + className?: string; + /** + * Enables or disables browser spell checking + */ + spellCheck?: boolean; +}; + +/** + * Component for displaying a textarea with customizable size, rows, and error state + */ +export function TextArea({ + value, + defaultValue, + placeholder, + size = 'medium', + maxRows, + minRows, + disabled, + error, + onChange, + onClick, + onBlur, + className, + spellCheck, + ...props +}: TextAreaProps) { + const containerClasses = clsx( + styles['text-area-container'], + inputSizeStyles[size], + { + 'base--error': error, + 'base--disabled': disabled, + }, + className, + ); + + const textareaClasses = clsx(styles['text-area'], inputFontStyles[size]); + + return ( +
{ + if (event.target !== event.currentTarget) return; + event.preventDefault(); + event.currentTarget.querySelector('textarea')?.focus(); + }} + > + +
+ ); +} diff --git a/packages/ui/src/components/tooltip/index.ts b/packages/ui/src/components/tooltip/index.ts new file mode 100644 index 000000000..6ed2401d1 --- /dev/null +++ b/packages/ui/src/components/tooltip/index.ts @@ -0,0 +1,4 @@ +export * from './tooltip'; +export * from './tooltip-content'; +export * from './tooltip-trigger'; +export * from './types'; diff --git a/packages/ui/src/components/tooltip/placement.spec.ts b/packages/ui/src/components/tooltip/placement.spec.ts new file mode 100644 index 000000000..1a0685827 --- /dev/null +++ b/packages/ui/src/components/tooltip/placement.spec.ts @@ -0,0 +1,15 @@ +import { placementToSideAlign } from './placement'; + +describe('placementToSideAlign', () => { + it('defaults align to center for a bare side', () => { + expect(placementToSideAlign('bottom')).toEqual({ side: 'bottom', align: 'center' }); + }); + + it('splits a side-start placement', () => { + expect(placementToSideAlign('top-start')).toEqual({ side: 'top', align: 'start' }); + }); + + it('splits a side-end placement', () => { + expect(placementToSideAlign('right-end')).toEqual({ side: 'right', align: 'end' }); + }); +}); diff --git a/packages/ui/src/components/tooltip/placement.ts b/packages/ui/src/components/tooltip/placement.ts new file mode 100644 index 000000000..1417dd5fa --- /dev/null +++ b/packages/ui/src/components/tooltip/placement.ts @@ -0,0 +1,17 @@ +type Side = 'top' | 'right' | 'bottom' | 'left'; +type Align = 'start' | 'end'; + +export type TooltipPlacement = Side | `${Side}-${Align}`; + +export type PlacementContextValue = { + side: Side; + align: 'start' | 'center' | 'end'; +}; + +export function placementToSideAlign(placement: TooltipPlacement): PlacementContextValue { + const [side, align] = placement.split('-') as [ + PlacementContextValue['side'], + PlacementContextValue['align'] | undefined, + ]; + return { side, align: align ?? 'center' }; +} diff --git a/packages/ui/src/components/tooltip/tooltip-content.tsx b/packages/ui/src/components/tooltip/tooltip-content.tsx new file mode 100644 index 000000000..3c240e6ff --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip-content.tsx @@ -0,0 +1,48 @@ +import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; +import clsx from 'clsx'; +import { forwardRef } from 'react'; + +import styles from './tooltip.module.css'; + +import { useTooltipPlacement } from './tooltip-context'; +import { TooltipVariant } from './types'; + +const TOOLTIP_OFFSET = 10; +const TOOLTIP_COLLISION_PADDING = 5; + +export const TooltipContent = forwardRef< + HTMLDivElement, + React.HTMLProps & { + /** + * TooltipType determines the color type of the tooltip + */ + tooltipType?: TooltipVariant; + } +>(function TooltipContent({ style, tooltipType = 'default', children, className, ...props }, propertyRef) { + const { side, align } = useTooltipPlacement(); + + if (!children) return null; + + return ( + + + + {children} + + + + + ); +}); diff --git a/packages/ui/src/components/tooltip/tooltip-context.ts b/packages/ui/src/components/tooltip/tooltip-context.ts new file mode 100644 index 000000000..3dc465e0d --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip-context.ts @@ -0,0 +1,17 @@ +import { createContext, useContext } from 'react'; + +import { type PlacementContextValue } from './placement'; + +export const TOOLTIP_OPEN_DELAY = 500; +export const TOOLTIP_CLOSE_DELAY = 0; + +const TooltipPlacementContext = createContext({ + side: 'bottom', + align: 'center', +}); + +export const TooltipPlacementProvider = TooltipPlacementContext.Provider; + +export function useTooltipPlacement(): PlacementContextValue { + return useContext(TooltipPlacementContext); +} diff --git a/packages/ui/src/components/tooltip/tooltip-trigger.tsx b/packages/ui/src/components/tooltip/tooltip-trigger.tsx new file mode 100644 index 000000000..9d9718238 --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip-trigger.tsx @@ -0,0 +1,45 @@ +import { mergeProps } from '@base-ui/react/merge-props'; +import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; +import { ReactElement, cloneElement, forwardRef, isValidElement } from 'react'; + +import { TOOLTIP_CLOSE_DELAY, TOOLTIP_OPEN_DELAY } from './tooltip-context'; + +/** + * Tooltips trigger is the the element that toggles the tooltip + */ +export const TooltipTrigger = forwardRef< + HTMLElement, + React.HTMLProps & { + /** + * `asChild` allows the user to pass any element as the anchor + */ + asChild?: boolean; + } +>(function TooltipTrigger({ children, asChild = false, ...props }, propertyRef) { + return ( + } + delay={TOOLTIP_OPEN_DELAY} + closeDelay={TOOLTIP_CLOSE_DELAY} + closeOnClick={false} + render={(triggerProps, state) => { + const dataState = state.open ? 'open' : 'closed'; + + if (asChild && isValidElement(children)) { + const childElement = children as ReactElement>; + // A plain spread would let a child's handlers replace Base UI's and break the tooltip. + return cloneElement(childElement, { + ...mergeProps(triggerProps, props as Record, childElement.props ?? {}), + 'data-state': dataState, + }); + } + + return ( +
)} data-state={dataState}> + {children} +
+ ); + }} + /> + ); +}); diff --git a/packages/ui/src/components/tooltip/tooltip.module.css b/packages/ui/src/components/tooltip/tooltip.module.css new file mode 100644 index 000000000..79c6f50dd --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip.module.css @@ -0,0 +1,35 @@ +:root { + --ax-public-tooltip-default-background: var(--ax-tooltip-bg-default); + --ax-public-tooltip-blue-background: var(--ax-tooltip-bg-blue); + --ax-public-tooltip-padding: var(--ax-token-spacing-tooltip-v-pad) var(--ax-token-spacing-tooltip-h-pad); + --ax-public-tooltip-radius-size: var(--ax-token-radius-tooltip); + --ax-public-tooltip-text-color: var(--ax-txt-tooltip-bw); + --ax-public-tooltip-blue-text-color: var(--ax-txt-tooltip-blue); +} + +@layer ui.component { + .container { + --tooltip-background: var(--ax-public-tooltip-default-background); + --tooltip-text-color: var(--ax-public-tooltip-text-color); + + padding: var(--ax-public-tooltip-padding); + border-radius: var(--ax-public-tooltip-radius-size); + width: max-content; + max-width: 33vw; + z-index: 100; + color: var(--tooltip-text-color); + background-color: var(--tooltip-background); + } + + .container[data-tooltip-type='blue'] { + --tooltip-background: var(--ax-public-tooltip-blue-background); + --tooltip-text-color: var(--ax-public-tooltip-blue-text-color); + } + + .arrow { + width: 10px; + height: 4px; + background-color: var(--tooltip-background); + clip-path: polygon(0 0, 100% 0, 50% 100%); + } +} diff --git a/packages/ui/src/components/tooltip/tooltip.tsx b/packages/ui/src/components/tooltip/tooltip.tsx new file mode 100644 index 000000000..e231c3d6a --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -0,0 +1,69 @@ +import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; +import { ReactNode, useMemo } from 'react'; + +import { type TooltipPlacement, placementToSideAlign } from './placement'; +import { TooltipContent } from './tooltip-content'; +import { TooltipPlacementProvider } from './tooltip-context'; +import { TooltipTrigger } from './tooltip-trigger'; + +export type { TooltipPlacement } from './placement'; + +export type TooltipOptions = { + /** + * If true, the component is shown at initial + */ + initialOpen?: boolean; + /** + * Tooltip placement. + */ + placement?: TooltipPlacement; + /** + * If true, the component is shown. + */ + open?: boolean; + /** + * Callback fired when the component requests to be open. + */ + onOpenChange?: (open: boolean) => void; +}; + +type Props = { + /** + * Tooltip reference element. + */ + children: ReactNode; +} & TooltipOptions; + +const HOVER_FOCUS_REASONS = new Set(['trigger-hover', 'trigger-focus', 'focus-out']); + +/** + * Tooltips display informative text when users hover over, focus on, or tap an element. + */ +export function Tooltip({ children, initialOpen, placement = 'bottom', open, onOpenChange }: Props) { + const isControlled = open !== undefined; + const placementValue = useMemo(() => placementToSideAlign(placement), [placement]); + + return ( + + { + if (isControlled && HOVER_FOCUS_REASONS.has(eventDetails.reason)) { + return; + } + onOpenChange(nextOpen); + } + : undefined + } + > + {children} + + + ); +} + +Tooltip.Content = TooltipContent; +Tooltip.Trigger = TooltipTrigger; diff --git a/packages/ui/src/components/tooltip/types.ts b/packages/ui/src/components/tooltip/types.ts new file mode 100644 index 000000000..904906559 --- /dev/null +++ b/packages/ui/src/components/tooltip/types.ts @@ -0,0 +1 @@ +export type TooltipVariant = 'default' | 'blue'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 000000000..66c82ded0 --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1,29 @@ +import './styles/globals.css'; +import './styles/layers.css'; +import './styles/typography.css'; + +export * from './components/accordion'; +export * from './components/avatar'; +export * from './components/button'; +export * from './components/checkbox'; +export * from './components/collapsible'; +export * from './components/date-picker'; +export * from './components/edge'; +export * from './components/input'; +export * from './components/menu'; +export * from './components/modal'; +export * from './components/node'; +export * from './components/radio-button'; +export * from './components/segment-picker'; +export * from './components/select'; +export * from './components/separator'; +export * from './components/snackbar'; +export * from './components/status'; +export * from './components/switch'; +export * from './components/text-area'; +export * from './components/tooltip'; + +// Shared types +export * from './shared/types/size'; +export * from './shared/types/item-size'; +export * from './shared/types/with-icon'; diff --git a/packages/ui/src/shared/hooks/use-transition-event.ts b/packages/ui/src/shared/hooks/use-transition-event.ts new file mode 100644 index 000000000..2ef32fdec --- /dev/null +++ b/packages/ui/src/shared/hooks/use-transition-event.ts @@ -0,0 +1,26 @@ +import { RefObject, useEffect } from 'react'; + +type TransitionEventType = 'transitionstart' | 'transitionrun' | 'transitionend'; + +export function useTransitionEvent( + ref: RefObject, + type: TransitionEventType, + propertyName: string, + callback: () => void, +) { + useEffect(() => { + const element = ref.current; + if (!element) { + return; + } + + const handler = (event: TransitionEvent) => { + if (event.propertyName === propertyName) { + callback(); + } + }; + + element.addEventListener(type, handler); + return () => element.removeEventListener(type, handler); + }, [ref, type, propertyName, callback]); +} diff --git a/packages/ui/src/shared/styles/input-font-size.module.css b/packages/ui/src/shared/styles/input-font-size.module.css new file mode 100644 index 000000000..93cb8746e --- /dev/null +++ b/packages/ui/src/shared/styles/input-font-size.module.css @@ -0,0 +1,10 @@ +@layer ui.component { + .large { + composes: ax-public-p9 from global; + } + + .medium, + .small { + composes: ax-public-p10 from global; + } +} diff --git a/packages/ui/src/shared/styles/input-size.module.css b/packages/ui/src/shared/styles/input-size.module.css new file mode 100644 index 000000000..a8848c42d --- /dev/null +++ b/packages/ui/src/shared/styles/input-size.module.css @@ -0,0 +1,33 @@ +:root { + --ax-public-input-padding-large: var(--ax-token-spacing-input-l-v-pad) var(--ax-token-spacing-input-l-h-pad); + --ax-public-input-padding-medium: var(--ax-token-spacing-input-m-v-pad) var(--ax-token-spacing-input-m-h-pad); + --ax-public-input-padding-small: var(--ax-token-spacing-input-s-v-pad) var(--ax-token-spacing-input-s-h-pad); + + --ax-public-input-border-radius-large: var(--ax-token-radius-input-l); + --ax-public-input-border-radius-medium: var(--ax-token-radius-input-m); + --ax-public-input-border-radius-small: var(--ax-token-radius-input-s); + + --ax-public-input-gap-large: var(--ax-token-spacing-input-l-gap); + --ax-public-input-gap-medium: var(--ax-token-spacing-input-m-gap); + --ax-public-input-gap-small: var(--ax-token-spacing-input-s-gap); +} + +@layer ui.component { + .large { + padding: var(--ax-public-input-padding-large); + border-radius: var(--ax-public-input-border-radius-large); + gap: var(--ax-public-input-gap-large); + } + + .medium { + padding: var(--ax-public-input-padding-medium); + border-radius: var(--ax-public-input-border-radius-medium); + gap: var(--ax-public-input-gap-medium); + } + + .small { + padding: var(--ax-public-input-padding-small); + border-radius: var(--ax-public-input-border-radius-small); + gap: var(--ax-public-input-gap-small); + } +} diff --git a/packages/ui/src/shared/styles/list-box.module.css b/packages/ui/src/shared/styles/list-box.module.css new file mode 100644 index 000000000..9890bdca8 --- /dev/null +++ b/packages/ui/src/shared/styles/list-box.module.css @@ -0,0 +1,49 @@ +:root { + --ax-public-list-box-background-color: var(--ax-dropdown-bg-primary-default); + --ax-public-list-box-border-radius: var(--ax-token-radius-dropdown-wrap-s); + --ax-public-list-box-box-shadow: var(--ax-token-shadow-shadow-s-x) var(--ax-token-shadow-shadow-s-y) + var(--ax-token-shadow-shadow-s-blur) var(--ax-token-shadow-shadow-s-spread) var(--ax-shadow); + --ax-public-list-box-padding: var(--ax-token-spacing-dropdown-wrap-s-v-pad) + var(--ax-token-spacing-dropdown-wrap-s-h-pad); + --ax-public-list-box-gap: var(--ax-token-spacing-dropdown-wrap-s-gap); +} + +@layer ui.component { + /* Applied to the Base UI Positioner — keep it free of visuals and + animation: Base UI sets data-starting-style/data-ending-style on the + Popup element only, so transition rules here would never fire. */ + .popup { + margin-top: 0.25rem; + z-index: 10000; + } + + /* Applied to the Base UI Popup, which carries the transition state + attributes (same pattern as Tooltip.Popup and Dialog.Popup). */ + .list-box { + display: flex; + padding: var(--ax-public-list-box-padding); + flex-direction: column; + align-items: center; + gap: var(--ax-public-list-box-gap); + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; + + margin: 0; + + border-radius: var(--ax-public-list-box-border-radius); + background: var(--ax-public-list-box-background-color); + box-shadow: var(--ax-public-list-box-box-shadow); + + transition: + opacity 150ms ease-out, + transform 150ms ease-out; + transform-origin: top; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scaleY(0.96); + } + } +} diff --git a/packages/ui/src/shared/styles/list-item-size.module.css b/packages/ui/src/shared/styles/list-item-size.module.css new file mode 100644 index 000000000..8590c28f4 --- /dev/null +++ b/packages/ui/src/shared/styles/list-item-size.module.css @@ -0,0 +1,31 @@ +:root { + --ax-public-list-item-padding-large: var(--ax-token-spacing-dropdown-item-l-v-pad) + var(--ax-token-spacing-dropdown-item-l-h-pad); + --ax-public-list-item-padding-medium: var(--ax-token-spacing-dropdown-item-m-v-pad) + var(--ax-token-spacing-dropdown-item-m-h-pad); + --ax-public-list-item-padding-small: var(--ax-token-spacing-dropdown-item-s-v-pad) + var(--ax-token-spacing-dropdown-item-s-h-pad); + --ax-public-list-item-gap-small: var(--ax-token-spacing-dropdown-item-s-gap); + --ax-public-list-item-gap-medium: var(--ax-token-spacing-dropdown-item-m-gap); + --ax-public-list-item-gap-large: var(--ax-token-spacing-dropdown-item-l-gap); +} + +@layer ui.component { + .large { + composes: ax-public-p9 from global; + padding: var(--ax-public-list-item-padding-large); + gap: var(--ax-public-list-item-gap-large); + } + + .medium { + composes: ax-public-p10 from global; + padding: var(--ax-public-list-item-padding-medium); + gap: var(--ax-public-list-item-gap-medium); + } + + .small { + composes: ax-public-p10 from global; + padding: var(--ax-public-list-item-padding-small); + gap: var(--ax-public-list-item-gap-small); + } +} diff --git a/packages/ui/src/shared/styles/list-item.module.css b/packages/ui/src/shared/styles/list-item.module.css new file mode 100644 index 000000000..fd1b440f9 --- /dev/null +++ b/packages/ui/src/shared/styles/list-item.module.css @@ -0,0 +1,69 @@ +:root { + --ax-public-list-item-background-color: var(--ax-dropdown-bg-secondary-default); + --ax-public-list-item-background-color-selected: var(--ax-dropdown-bg-secondary-active); + --ax-public-list-item-color-selected: var(--ax-txt-primary-white); + --ax-public-list-item-color: var(--ax-txt-primary-default); + + --ax-public-list-item-background-color-focus: var(--ax-dropdown-bg-secondary-default); + --ax-public-list-item-outline-color-focus: var(--ax-ui-stroke-primary-focus); + --ax-public-list-item-outline-size: 0.0625rem; + + --ax-public-list-item-color-destructive: var(--ax-txt-error-default); + --ax-public-list-item-background-color-destructive: var(--ax-dropdown-bg-destructive-default); + --ax-public-list-item-background-color-hover-destructive: var(--ax-dropdown-bg-destructive-hover); + --ax-public-list-border-radius: var(--ax-token-radius-dropdown-item-s); +} + +@layer ui.component { + .list-item { + display: flex; + align-items: center; + border-radius: var(--ax-public-list-border-radius); + width: max-content; + min-width: 100%; + cursor: pointer; + color: var(--ax-public-list-item-color); + + /* State selectors below weigh the same, so order decides - a `:not()` here + would silently outrank the states under it. */ + &:hover { + background-color: var(--ax-public-list-item-background-color); + } + + &:focus-visible, + &[data-highlighted] { + outline: var(--ax-public-list-item-outline-size) solid var(--ax-public-list-item-outline-color-focus); + background-color: var(--ax-public-list-item-background-color-focus); + } + + /* After the highlight rule: a selected item keeps its selected colors + while highlighted — the outline alone marks the keyboard cursor. */ + &[data-selected] { + background-color: var(--ax-public-list-item-background-color-selected); + color: var(--ax-public-list-item-color-selected); + } + + &[data-disabled] { + color: var(--ax-public-input-color-disabled); + background-color: transparent; + cursor: auto; + } + + &.destructive { + color: var(--ax-public-list-item-color-destructive); + background-color: var(--ax-public-list-item-background-color-destructive); + + &:hover, + &:focus-visible, + &[data-highlighted] { + background-color: var(--ax-public-list-item-background-color-hover-destructive); + } + } + + svg { + width: 1rem; + height: 1rem; + min-width: 1rem; + } + } +} diff --git a/packages/ui/src/shared/types/item-size.ts b/packages/ui/src/shared/types/item-size.ts new file mode 100644 index 000000000..fd3e22eeb --- /dev/null +++ b/packages/ui/src/shared/types/item-size.ts @@ -0,0 +1,3 @@ +import type { Size } from './size'; + +export type ItemSize = Extract; diff --git a/packages/ui/src/shared/types/list-item.ts b/packages/ui/src/shared/types/list-item.ts new file mode 100644 index 000000000..d6e12cfe3 --- /dev/null +++ b/packages/ui/src/shared/types/list-item.ts @@ -0,0 +1,7 @@ +import { WithIcon } from '../../shared/types/with-icon'; + +export type ListItem = Partial & { + type?: 'item' | 'separator'; + label?: string; + disabled?: boolean; +}; diff --git a/packages/ui/src/shared/types/selector-size.ts b/packages/ui/src/shared/types/selector-size.ts new file mode 100644 index 000000000..7a3cb3188 --- /dev/null +++ b/packages/ui/src/shared/types/selector-size.ts @@ -0,0 +1,3 @@ +import { Size } from '@ui/shared/types/size'; + +export type SelectorSize = Extract; diff --git a/packages/ui/src/shared/types/size.ts b/packages/ui/src/shared/types/size.ts new file mode 100644 index 000000000..9a81798ac --- /dev/null +++ b/packages/ui/src/shared/types/size.ts @@ -0,0 +1,2 @@ +export const SIZES = ['xxx-small', 'xx-small', 'extra-small', 'small', 'medium', 'large', 'extra-large'] as const; +export type Size = (typeof SIZES)[number]; diff --git a/packages/ui/src/shared/types/with-icon.ts b/packages/ui/src/shared/types/with-icon.ts new file mode 100644 index 000000000..948283570 --- /dev/null +++ b/packages/ui/src/shared/types/with-icon.ts @@ -0,0 +1,6 @@ +export type WithIcon = { + /** + * Icon content React Node + */ + icon?: React.ReactNode; +}; diff --git a/packages/ui/src/shared/utils/arrays.spec.ts b/packages/ui/src/shared/utils/arrays.spec.ts new file mode 100644 index 000000000..c19ad6c4d --- /dev/null +++ b/packages/ui/src/shared/utils/arrays.spec.ts @@ -0,0 +1,26 @@ +import { rangeBetween } from './arrays'; + +const list = ['a', 'b', 'c', 'd', 'e'] as const; + +describe('rangeBetween', () => { + it('returns an inclusive forward range', () => { + expect(rangeBetween(list, 'b', 'd')).toEqual(['b', 'c', 'd']); + }); + + it('returns a single element when from equals to', () => { + expect(rangeBetween(list, 'c', 'c')).toEqual(['c']); + }); + + it('returns an empty array for a reversed range', () => { + expect(rangeBetween(list, 'd', 'b')).toEqual([]); + }); + + it('returns an empty array when a value is not in the list', () => { + expect(rangeBetween(list, 'a', 'z' as (typeof list)[number])).toEqual([]); + expect(rangeBetween(list, 'z' as (typeof list)[number], 'c')).toEqual([]); + }); + + it('covers the full range at list bounds', () => { + expect(rangeBetween(list, 'a', 'e')).toEqual(['a', 'b', 'c', 'd', 'e']); + }); +}); diff --git a/packages/ui/src/shared/utils/arrays.ts b/packages/ui/src/shared/utils/arrays.ts new file mode 100644 index 000000000..0148dc807 --- /dev/null +++ b/packages/ui/src/shared/utils/arrays.ts @@ -0,0 +1,6 @@ +export function rangeBetween(list: T, from: T[number], to: T[number]): T[number][] { + const fromIndex = list.indexOf(from); + const toIndex = list.indexOf(to); + + return fromIndex !== -1 && toIndex !== -1 && fromIndex <= toIndex ? list.slice(fromIndex, toIndex + 1) : []; +} diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css new file mode 100644 index 000000000..45d105cc3 --- /dev/null +++ b/packages/ui/src/styles/globals.css @@ -0,0 +1,7 @@ +/* Wrapped by hand (not by the build plugin): this file is concatenated raw + into dist/styles.css, bypassing the PostCSS pipeline. */ +@layer ui.base { + :root { + --ax-public-transition: 0.1s ease-in; + } +} diff --git a/packages/ui/src/styles/layers.css b/packages/ui/src/styles/layers.css new file mode 100644 index 000000000..84a5d1502 --- /dev/null +++ b/packages/ui/src/styles/layers.css @@ -0,0 +1 @@ +@layer ui.base, ui.component; diff --git a/packages/ui/src/styles/typography.css b/packages/ui/src/styles/typography.css new file mode 100644 index 000000000..f14dfa579 --- /dev/null +++ b/packages/ui/src/styles/typography.css @@ -0,0 +1,169 @@ +@layer ui.base { + /* Heading and paragraph styles */ + + .ax-public-h1, + .ax-public-h2, + .ax-public-h3, + .ax-public-h4, + .ax-public-h5, + .ax-public-h6, + .ax-public-h7, + .ax-public-h8, + .ax-public-h9, + .ax-public-h10, + .ax-public-h11, + .ax-public-h12 { + font-weight: 600; + } + + .ax-public-p1, + .ax-public-p2, + .ax-public-p3, + .ax-public-p4, + .ax-public-p5, + .ax-public-p6, + .ax-public-p7, + .ax-public-p8, + .ax-public-p9, + .ax-public-p10, + .ax-public-p11, + .ax-public-p12 { + font-weight: 400; + } + + .ax-public-h1, + .ax-public-h2, + .ax-public-h3, + .ax-public-p1, + .ax-public-p2, + .ax-public-p3 { + line-height: 120%; + } + + .ax-public-h4, + .ax-public-h5, + .ax-public-h6, + .ax-public-p4, + .ax-public-p5, + .ax-public-p6 { + line-height: 130%; + } + + .ax-public-h7, + .ax-public-h8, + .ax-public-h9, + .ax-public-h10, + .ax-public-h11, + .ax-public-h12, + .ax-public-p7, + .ax-public-p8, + .ax-public-p9, + .ax-public-p10, + .ax-public-p11, + .ax-public-p12 { + line-height: 140%; + } + + .ax-public-h1, + .ax-public-p1 { + font-size: 2.25rem; + } + + .ax-public-h2, + .ax-public-p2 { + font-size: 2rem; + } + + .ax-public-h3, + .ax-public-p3 { + font-size: 1.75rem; + } + + .ax-public-h4, + .ax-public-p4 { + font-size: 1.5rem; + } + + .ax-public-h5, + .ax-public-p5 { + font-size: 1.25rem; + } + + .ax-public-h6, + .ax-public-p6 { + font-size: 1.125rem; + } + + .ax-public-h7, + .ax-public-p7 { + font-size: 1rem; + } + + .ax-public-h8, + .ax-public-p8 { + font-size: 0.9375rem; + } + + .ax-public-h9, + .ax-public-p9 { + font-size: 0.875rem; + } + + .ax-public-h10, + .ax-public-p10 { + font-size: 0.75rem; + } + + .ax-public-h11, + .ax-public-p11 { + font-size: 0.625rem; + } + + .ax-public-h12, + .ax-public-p12 { + font-size: 0.5rem; + } + + /* Navigation and Links */ + + .ax-public-button-large, + .ax-public-button-medium, + .ax-public-button-small, + .ax-public-button-extra-small { + font-weight: 600; + line-height: 100%; + } + + .ax-public-button-large { + font-size: 1rem; + } + + .ax-public-button-medium { + font-size: 0.875rem; + } + + .ax-public-button-small { + font-size: 0.75rem; + } + + .ax-public-button-extra-small { + font-size: 0.625rem; + } + + /* Edge Label */ + + .ax-public-edge-label-medium { + font-size: 0.75rem; + line-height: 100%; + } + + .ax-public-edge-label-small { + font-size: 0.75rem; + line-height: 100%; + } + + .ax-public-edge-label-extra-small { + font-size: 0.625rem; + line-height: 100%; + } +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 000000000..9660df184 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vitest/globals"], + "paths": { + "@ui/*": ["./src/*"] + } + }, + "include": ["src", "types.d.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/ui/tsconfig.node.json b/packages/ui/tsconfig.node.json new file mode 100644 index 000000000..30bdc249a --- /dev/null +++ b/packages/ui/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "lib": ["ES2022"], + "types": ["node"] + }, + // Build tooling program, separate from the component program so TypeScript + // tools reading tsconfig.json (TypeDoc, vite-plugin-dts, IDEs) never pull + // build files in. *.spec.mts stays out - a vitest-importing spec in a ts + // program stalls tsc for minutes. + "include": ["postcss-box-sizing.mts", "postcss-layer-root-defaults.mts", "combine-css-bundle.mts", "scripts"], + "exclude": ["node_modules", "dist", "**/*.spec.mts"] +} diff --git a/packages/ui/types.d.ts b/packages/ui/types.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/ui/types.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts new file mode 100644 index 000000000..dfc38300b --- /dev/null +++ b/packages/ui/vite.config.mts @@ -0,0 +1,143 @@ +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { visualizer } from 'rollup-plugin-visualizer'; +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; + +import { libInjectCss } from 'vite-plugin-lib-inject-css'; + +import { combineCssBundle } from './combine-css-bundle.mts'; +import { boxSizingPlugin } from './postcss-box-sizing.mts'; +import { layerRootDefaultsPlugin } from './postcss-layer-root-defaults.mts'; + +const rootDirectory = path.dirname(fileURLToPath(import.meta.url)); + +const componentEntries = [ + 'accordion', + 'avatar', + 'button', + 'checkbox', + 'collapsible', + 'date-picker', + 'edge', + 'input', + 'menu', + 'modal', + 'node', + 'radio-button', + 'segment-picker', + 'select', + 'separator', + 'snackbar', + 'status', + 'switch', + 'text-area', + 'tooltip', +] as const; + +const externalPackages = ['@base-ui/react', '@phosphor-icons/react', 'react-textarea-autosize']; +const externalModules = new Set(['react', 'react-dom', 'react/jsx-runtime']); + +function getEntries(): Record { + const entries: Record = { + index: path.resolve(rootDirectory, 'src/index.ts'), + }; + for (const name of componentEntries) { + entries[name] = path.resolve(rootDirectory, `src/components/${name}/index.ts`); + } + return entries; +} + +function isExternal(id: string): boolean { + if (externalModules.has(id)) return true; + return externalPackages.some((package_) => id === package_ || id.startsWith(`${package_}/`)); +} + +function copyTokenStyles() { + const files = ['tokens.css', 'numerals-mode-1.css', 'primitives-mode-1.css']; + + for (const file of files) { + if (!existsSync(path.resolve(rootDirectory, `../tokens/dist/${file}`))) { + throw new Error( + `@workflowbuilder/ui-tokens dist is missing ${file} - ` + + 'build the tokens first: `pnpm build:ui` (root) or `pnpm --filter @workflowbuilder/ui-tokens build`', + ); + } + } + + const layerOrder = readFileSync(path.resolve(rootDirectory, 'src/styles/layers.css'), 'utf8').trim(); + + return viteStaticCopy({ + targets: files.map((file) => ({ + src: `../tokens/dist/${file}`, + dest: '.', + transform: (content: string) => `${layerOrder}\n@layer ui.base {\n${content}\n}`, + })), + }); +} + +function bundleStatsPlugins() { + return [ + visualizer({ + filename: 'dist/bundle-stats.html', + template: 'treemap', + gzipSize: true, + brotliSize: true, + }), + visualizer({ + filename: 'dist/bundle-stats.json', + json: true, + gzipSize: true, + brotliSize: true, + }), + ]; +} + +export default defineConfig({ + build: { + lib: { + entry: getEntries(), + name: '@workflowbuilder/ui', + formats: ['es'], + }, + rollupOptions: { + external: isExternal, + onwarn: (warning, warn) => { + if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return; + warn(warning); + }, + output: { + entryFileNames: '[name].js', + chunkFileNames: 'chunks/[name]-[hash].js', + assetFileNames: 'assets/[name][extname]', + globals: { + 'react-dom': 'ReactDom', + react: 'React', + 'react/jsx-runtime': 'ReactJsxRuntime', + }, + }, + }, + }, + css: { + devSourcemap: true, + postcss: { + plugins: [boxSizingPlugin(), layerRootDefaultsPlugin()], + }, + }, + resolve: { + alias: { + '@ui': path.resolve(rootDirectory, './src'), + }, + }, + plugins: [ + libInjectCss(), + // A stray *.spec.mts in the ts program stalls this build for 15+ minutes + // with no output - keep every spec shape excluded. + dts({ entryRoot: 'src', exclude: ['**/*.spec.{ts,tsx,mts}'] }), + copyTokenStyles(), + combineCssBundle(rootDirectory), + ...(process.env.BUNDLE_STATS ? bundleStatsPlugins() : []), + ], +}); diff --git a/packages/ui/vitest.config.mts b/packages/ui/vitest.config.mts new file mode 100644 index 000000000..0cb68ba90 --- /dev/null +++ b/packages/ui/vitest.config.mts @@ -0,0 +1,18 @@ +/// +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const rootDirectory = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + resolve: { + alias: { + '@ui': path.resolve(rootDirectory, './src'), + }, + }, + test: { + globals: true, + environment: 'jsdom', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0812c5f9f..7765e7588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,15 @@ settings: catalogs: default: + '@base-ui/react': + specifier: 1.4.1 + version: 1.4.1 '@phosphor-icons/core': specifier: ^2.1.1 version: 2.1.1 + '@phosphor-icons/react': + specifier: ^2.1.7 + version: 2.1.7 '@types/react': specifier: 19.1.8 version: 19.1.8 @@ -118,7 +124,7 @@ importers: version: 10.1.1 mermaid: specifier: ^11.15.0 - version: 11.15.0 + version: 11.16.0 react: specifier: ^19.1.0 version: 19.1.0 @@ -133,13 +139,13 @@ importers: version: 10.1.0(@types/react@19.1.8)(react@19.1.0) recharts: specifier: ^3.9.0 - version: 3.9.0(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1) + version: 3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1) remark-gfm: specifier: ^4.0.1 version: 4.0.1 zustand: specifier: ^5.0.1 - version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)) + version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@types/react': specifier: 'catalog:' @@ -264,7 +270,7 @@ importers: version: 1.5.0 zustand: specifier: ^5.0.1 - version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)) + version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@types/express': specifier: ^5.0.5 @@ -540,10 +546,98 @@ importers: version: 3.0.4(@types/debug@4.1.12)(@types/node@22.12.0)(jiti@2.6.1)(jsdom@26.0.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) zustand: specifier: ^5.0.1 - version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)) + version: 5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) + + packages/tokens: + devDependencies: + '@tokens-studio/sd-transforms': + specifier: ^1.2.12 + version: 1.3.0(style-dictionary@4.4.0(tslib@2.8.1)) + remeda: + specifier: ^2.21.2 + version: 2.39.0 + style-dictionary: + specifier: ^4.3.3 + version: 4.4.0(tslib@2.8.1) + tsx: + specifier: ^4.19.3 + version: 4.21.0 + vitest: + specifier: ^3.0.4 + version: 3.0.4(@types/debug@4.1.12)(@types/node@22.12.0)(jiti@2.6.1)(jsdom@26.0.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) packages/types: {} + packages/ui: + dependencies: + '@base-ui/react': + specifier: 'catalog:' + version: 1.4.1(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@phosphor-icons/react': + specifier: 'catalog:' + version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-textarea-autosize: + specifier: ^8.5.6 + version: 8.5.9(@types/react@19.1.8)(react@19.1.0) + devDependencies: + '@types/react': + specifier: 'catalog:' + version: 19.1.8 + '@types/react-dom': + specifier: ^19.1.0 + version: 19.1.1(@types/react@19.1.8) + '@workflowbuilder/ui-tokens': + specifier: workspace:* + version: link:../tokens + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + eslint-plugin-react: + specifier: ^7.37.4 + version: 7.37.5(eslint@9.19.0(jiti@2.6.1)) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.19.0(jiti@2.6.1)) + postcss: + specifier: ^8.5.3 + version: 8.5.6 + react: + specifier: 'catalog:' + version: 19.1.0 + react-day-picker: + specifier: ^9.14.0 + version: 9.14.0(react@19.1.0) + react-dom: + specifier: 'catalog:' + version: 19.1.0(react@19.1.0) + rollup-plugin-visualizer: + specifier: ^7.0.1 + version: 7.0.1(rollup@4.57.1) + tsx: + specifier: ^4.19.3 + version: 4.21.0 + typescript: + specifier: ~5.6.3 + version: 5.6.3 + vite: + specifier: ^6.2.2 + version: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) + vite-plugin-dts: + specifier: ^4.5.3 + version: 4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) + vite-plugin-lib-inject-css: + specifier: ^2.2.1 + version: 2.2.2(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) + vite-plugin-static-copy: + specifier: ^2.3.1 + version: 2.3.2(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) + vitest: + specifier: ^3.0.4 + version: 3.0.4(@types/debug@4.1.12)(@types/node@22.12.0)(jiti@2.6.1)(jsdom@26.0.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) + packages: '@ai-sdk/gateway@3.0.104': @@ -575,6 +669,64 @@ packages: '@asamuzakjp/css-color@2.8.3': resolution: {integrity: sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==} + '@ast-grep/napi-darwin-arm64@0.36.3': + resolution: {integrity: sha512-uM0Hrm5gcHqaBL64ktmPBFMTorTlPKWsUfi0E2Cg09GJfeYWvZmicCqgd7qVtjURmQvFQdb4JSqHIkJvws6Uqw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@ast-grep/napi-darwin-x64@0.36.3': + resolution: {integrity: sha512-wEMeQw8lRL66puG2m8m0kDRQDtubygj59HA/cmut2V5SPx/13BN3wuEk6JPv97gqGUCUGhG2+5Z6UZ/Ll2q01Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@ast-grep/napi-linux-arm64-gnu@0.36.3': + resolution: {integrity: sha512-sMsTMaUjW7SM8KPbLviCSBuM4zgJcwvie1yZI92HKSlFzC7ABe7X7UvyUREB+JwqccDVEL5yOJAjqB8eFSCizw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@ast-grep/napi-linux-arm64-musl@0.36.3': + resolution: {integrity: sha512-2XRmNYuovZu0Pa4J3or4PKMkQZnXXfpVcCrPwWB/2ytX7XUo+TWLgYE8rPVnJOyw5zujkveFb0XUrro9mQgLzw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@ast-grep/napi-linux-x64-gnu@0.36.3': + resolution: {integrity: sha512-mTwPRbBi1feGqR2b5TWC5gkEDeRi8wfk4euF5sKNihfMGHj6pdfINHQ3QvLVO4C7z0r/wgWLAvditFA0b997dg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@ast-grep/napi-linux-x64-musl@0.36.3': + resolution: {integrity: sha512-tMGPrT+zuZzJK6n1cD1kOii7HYZE9gUXjwtVNE/uZqXEaWP6lmkfoTMbLjnxEe74VQbmaoDGh1/cjrDBnqC6Uw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@ast-grep/napi-win32-arm64-msvc@0.36.3': + resolution: {integrity: sha512-7pFyr9+dyV+4cBJJ1I57gg6PDXP3GBQeVAsEEitzEruxx4Hb4cyNro54gGtlsS+6ty+N0t004tPQxYO2VrsPIg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@ast-grep/napi-win32-ia32-msvc@0.36.3': + resolution: {integrity: sha512-MPAgccH9VscRaFuEBMzDGPS+3c4cKNVGIVJ7WSNa1nZtLQ0eFEaPJ7pyDnCezgVSxfNFVYBvKyyF/vcm7Qc9+A==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@ast-grep/napi-win32-x64-msvc@0.36.3': + resolution: {integrity: sha512-TIVtuSbXhty9kaSEfr4ULWx5PAuUeGgUkFaR60lmOs7sGTWgpig+suwKfTmevoAblFknCW/aMHOwziwJoUZA6A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@ast-grep/napi@0.36.3': + resolution: {integrity: sha512-ExypohE8L7FvKBHxu7UpwcV9XVfyS+AqNZKyKIfxYwJyD9l7Gw6pmMYd7J2uopJsPEIUf44/emEFds6nFUx/dw==} + engines: {node: '>= 10'} + '@astrojs/check@0.9.6': resolution: {integrity: sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA==} hasBin: true @@ -716,6 +868,10 @@ packages: resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -732,9 +888,48 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@base-ui/react@1.4.1': + resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@date-fns/tz': ^1.2.0 + '@types/react': ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@date-fns/tz': + optional: true + '@types/react': + optional: true + date-fns: + optional: true + + '@base-ui/utils@0.2.8': + resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@bundled-es-modules/deepmerge@4.3.2': + resolution: {integrity: sha512-q8doe7ndrY2IolUOFIn0A0++JBX38pMhN7kFhTF4cnjIcILf6X6H2yWczInyv8ZFdR0lrE8088X8XS5efxXz8A==} + + '@bundled-es-modules/glob@10.4.2': + resolution: {integrity: sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==} + + '@bundled-es-modules/memfs@4.17.0': + resolution: {integrity: sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==} + + '@bundled-es-modules/postcss-calc-ast-parser@0.1.6': + resolution: {integrity: sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==} + '@capsizecss/unpack@4.0.0': resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} engines: {node: '>=18'} @@ -913,6 +1108,9 @@ packages: resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} engines: {node: '>=14'} + '@date-fns/tz@1.5.0': + resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -1518,21 +1716,36 @@ packages: '@floating-ui/core@1.6.9': resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + '@floating-ui/dom@1.6.13': resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@floating-ui/react-dom@2.1.2': resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + '@floating-ui/react@0.26.28': resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} @@ -1589,8 +1802,8 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} - '@iconify/utils@3.1.3': - resolution: {integrity: sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==} + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} @@ -1738,6 +1951,10 @@ packages: '@types/node': optional: true + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1928,8 +2145,8 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} - '@mermaid-js/parser@1.1.1': - resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} + '@mermaid-js/parser@1.2.0': + resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} '@microsoft/api-extractor-model@7.33.8': resolution: {integrity: sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==} @@ -2182,6 +2399,10 @@ packages: react: '>= 16.8' react-dom: '>= 16.8' + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -2602,6 +2823,10 @@ packages: react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tabby_ai/hijri-converter@1.0.5': + resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==} + engines: {node: '>=16.0.0'} + '@temporalio/activity@1.16.0': resolution: {integrity: sha512-hmWt7uAvgV+U0sr2x4Z8jIpk5PnwlA0qkcfSlFTrzhp0NYD6GXHa+IPwHCu0wkkmjqfYwYEi/+fabApvsjmHRA==} engines: {node: '>= 20.0.0'} @@ -2653,6 +2878,15 @@ packages: '@types/react-dom': optional: true + '@tokens-studio/sd-transforms@1.3.0': + resolution: {integrity: sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==} + engines: {node: '>=18.0.0'} + peerDependencies: + style-dictionary: ^4.3.0 || ^5.0.0-rc.0 + + '@tokens-studio/types@0.5.2': + resolution: {integrity: sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==} + '@trivago/prettier-plugin-sort-imports@6.0.0': resolution: {integrity: sha512-Xarx55ow0R8oC7ViL5fPmDsg1EBa1dVhyZFVbFXNtPPJyW2w9bJADIla8YFSaNG9N06XfcklA9O9vmw4noNxkQ==} engines: {node: '>= 20'} @@ -2759,8 +2993,8 @@ packages: '@types/d3-quadtree@3.0.6': resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - '@types/d3-random@3.0.3': - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} '@types/d3-scale-chromatic@3.1.0': resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} @@ -3118,11 +3352,18 @@ packages: '@xyflow/system@0.0.74': resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==} + '@yarnpkg/lockfile@1.1.0': + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + '@yeskunall/astro-umami@0.0.7': resolution: {integrity: sha512-8W/At28qKyPGepwvVumgJX0VZ0F9KK4M4zNDQfUZ3iBJpAKUTAkdIXM7i7I+PFsP7rXOo/AVgcDXDOv9L03QhQ==} peerDependencies: astro: ^3.0.0 || ^4.0.0 || ^5.0.0 + '@zip.js/zip.js@2.8.26': + resolution: {integrity: sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -3316,6 +3557,9 @@ packages: assert@2.0.0: resolution: {integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==} + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -3383,6 +3627,9 @@ packages: resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} engines: {node: '>= 0.6.0'} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.19: resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==} engines: {node: '>=6.0.0'} @@ -3398,6 +3645,10 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + body-parser@2.2.0: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} @@ -3439,10 +3690,17 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + builtin-modules@5.0.0: resolution: {integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==} engines: {node: '>=18.20'} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -3455,6 +3713,10 @@ packages: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} @@ -3463,6 +3725,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3500,6 +3766,9 @@ packages: chance@1.1.9: resolution: {integrity: sha512-TfxnA/DcZXRTA4OekA2zL9GH8qscbbl6X0ZqU4tXhGveVY/mXWvEQLt5GwZcYXTEyEFflVtj+pG8nc8EwSm1RQ==} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -3533,6 +3802,10 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -3549,6 +3822,10 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + ci-info@4.2.0: resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} engines: {node: '>=8'} @@ -3611,6 +3888,9 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -3626,6 +3906,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} @@ -3650,6 +3934,10 @@ packages: compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + component-emitter@2.0.0: + resolution: {integrity: sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==} + engines: {node: '>=18'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -3971,6 +4259,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} @@ -4014,10 +4305,26 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -4105,8 +4412,8 @@ packages: dompurify@3.3.0: resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} - dompurify@3.4.11: - resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -4226,6 +4533,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -4244,6 +4554,9 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -4392,12 +4705,24 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.2: resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-tsdoc@0.4.0: resolution: {integrity: sha512-MT/8b4aKLdDClnS8mP3R/JNjg29i0Oyqd/0ym6NnQf+gfKbJJ4ZcSh2Bs1H0YiUMTBwww5JwXGTWot/RwyJ7aQ==} @@ -4517,6 +4842,9 @@ packages: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} + expr-eval-fork@2.0.2: + resolution: {integrity: sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==} + express@5.1.0: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} @@ -4605,6 +4933,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + find-yarn-workspace-root@2.0.0: + resolution: {integrity: sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -4627,6 +4958,10 @@ packages: resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.1: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} @@ -4644,6 +4979,10 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + fs-extra@11.3.4: resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} engines: {node: '>=14.14'} @@ -4690,6 +5029,10 @@ packages: resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -4734,6 +5077,11 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + global-directory@5.0.0: resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} engines: {node: '>=20'} @@ -4960,6 +5308,9 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4967,8 +5318,8 @@ packages: immer@10.1.1: resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} - immer@11.1.8: - resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + immer@11.1.15: + resolution: {integrity: sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==} import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -4989,6 +5340,9 @@ packages: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -5052,6 +5406,10 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-boolean-object@1.2.1: resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} engines: {node: '>= 0.4'} @@ -5082,6 +5440,11 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5118,6 +5481,10 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -5127,6 +5494,9 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-mergeable-object@1.1.1: + resolution: {integrity: sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==} + is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} @@ -5197,6 +5567,10 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + is-wsl@3.1.1: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} @@ -5211,6 +5585,9 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + javascript-natural-sort@0.7.1: resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} @@ -5290,6 +5667,10 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stable-stringify@1.3.0: + resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} + engines: {node: '>= 0.4'} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -5307,6 +5688,9 @@ packages: jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jsonify@0.0.1: + resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} + jspdf@3.0.3: resolution: {integrity: sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==} @@ -5324,6 +5708,9 @@ packages: khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + klaw-sync@6.0.0: + resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -5586,8 +5973,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.15.0: - resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -5864,6 +6251,10 @@ packages: resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} engines: {node: '>= 0.4'} + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} @@ -5895,6 +6286,14 @@ packages: oniguruma-to-es@4.3.4: resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + opencollective-postinstall@2.0.3: resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} hasBin: true @@ -5945,6 +6344,10 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + p-queue@8.1.1: resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} engines: {node: '>=18'} @@ -5957,6 +6360,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} @@ -6006,6 +6412,11 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + patch-package@8.0.1: + resolution: {integrity: sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==} + engines: {node: '>=14', npm: '>5'} + hasBin: true + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -6023,6 +6434,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -6030,6 +6445,12 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + path-unified@0.2.0: + resolution: {integrity: sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==} + + path@0.12.7: + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -6090,6 +6511,10 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-calc-ast-parser@0.1.4: + resolution: {integrity: sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==} + engines: {node: '>=6.5'} + postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} @@ -6104,6 +6529,9 @@ packages: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} + postcss-value-parser@3.3.1: + resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -6112,6 +6540,10 @@ packages: resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} engines: {node: '>=12'} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -6175,6 +6607,9 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6209,6 +6644,12 @@ packages: react: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-day-picker@9.14.0: + resolution: {integrity: sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -6324,6 +6765,10 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -6332,8 +6777,8 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - recharts@3.9.0: - resolution: {integrity: sha512-dCEcE9y20c8H2tkVeByrAXhhnBJk6/QLbxKmn+dJUptOfc5NMjwRh1jo0vZPRLD+5dMrHrP+hPEsfbGBMfnf5Q==} + recharts@3.10.1: + resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -6445,6 +6890,10 @@ packages: remeda@2.20.0: resolution: {integrity: sha512-T1qiOhoyzT0RToEbZn+saIkBvOxyFJrd4/Fr5PeR6x53PuGNq88mDAipmYhrsxbI+CmJGFVl/X+hoCkV6J8q/A==} + remeda@2.39.0: + resolution: {integrity: sha512-3Ki8dU1o3OVu4dwIQ2Pj+yiuP7OnEbmWAGmJ3yDRqopily5jsj8NWzPvbS89H85d6UdONKEcUnrfuHY6jN9vyw==} + engines: {node: '>=18.0.0'} + request-light@0.5.8: resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} @@ -6512,6 +6961,19 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup-plugin-visualizer@7.0.1: + resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} + engines: {node: '>=22'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + rollup@4.57.1: resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -6527,6 +6989,10 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -6666,6 +7132,10 @@ packages: engines: {node: '>=14.0.0', npm: '>=6.0.0'} hasBin: true + slash@2.0.0: + resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} + engines: {node: '>=6'} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -6758,6 +7228,9 @@ packages: stream-replace-string@2.0.0: resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} + stream@0.0.3: + resolution: {integrity: sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -6769,6 +7242,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -6792,6 +7269,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -6822,6 +7302,11 @@ packages: resolution: {integrity: sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==} engines: {node: '>=14.16'} + style-dictionary@4.4.0: + resolution: {integrity: sha512-+xU0IA1StzqAqFs/QtXkK+XJa7wpS4X5H+JQccRKsRCElgeLGocFU1U/UMvMUylKFw6vwGV+Y/a2wb2pm5rFFQ==} + engines: {node: '>=18.0.0'} + hasBin: true + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -6936,6 +7421,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -6966,6 +7454,10 @@ packages: resolution: {integrity: sha512-+lFzEXhpl7JXgWYaXcB6DqTYXbUArvrWAE/5ioq/X3CdWLbDjpPP4XTrQBmEJ91y3xbe4Fkw7Lxv4P3GWeJaNg==} hasBin: true + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -7242,6 +7734,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url@0.11.4: + resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} + engines: {node: '>= 0.4'} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -7294,9 +7790,17 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} @@ -7345,6 +7849,17 @@ packages: vite: optional: true + vite-plugin-lib-inject-css@2.2.2: + resolution: {integrity: sha512-NF30p0GwtfSAmVlxo2NgPXM2rEdtgV7LFi4lkzajKD7P3Ru/ZAFmI533M0Z5qyMZpvNMxVGkewzpjD0HOWtbDQ==} + peerDependencies: + vite: '*' + + vite-plugin-static-copy@2.3.2: + resolution: {integrity: sha512-iwrrf+JupY4b9stBttRWzGHzZbeMjAHBhkrn67MNACXJVjEMRpCI10Q3AkxdBkl45IHaTfw/CNVevzQhP7yTwg==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vite-plugin-svgr@4.3.0: resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==} peerDependencies: @@ -7612,6 +8127,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} @@ -7631,6 +8150,10 @@ packages: utf-8-validate: optional: true + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -7807,6 +8330,45 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 + '@ast-grep/napi-darwin-arm64@0.36.3': + optional: true + + '@ast-grep/napi-darwin-x64@0.36.3': + optional: true + + '@ast-grep/napi-linux-arm64-gnu@0.36.3': + optional: true + + '@ast-grep/napi-linux-arm64-musl@0.36.3': + optional: true + + '@ast-grep/napi-linux-x64-gnu@0.36.3': + optional: true + + '@ast-grep/napi-linux-x64-musl@0.36.3': + optional: true + + '@ast-grep/napi-win32-arm64-msvc@0.36.3': + optional: true + + '@ast-grep/napi-win32-ia32-msvc@0.36.3': + optional: true + + '@ast-grep/napi-win32-x64-msvc@0.36.3': + optional: true + + '@ast-grep/napi@0.36.3': + optionalDependencies: + '@ast-grep/napi-darwin-arm64': 0.36.3 + '@ast-grep/napi-darwin-x64': 0.36.3 + '@ast-grep/napi-linux-arm64-gnu': 0.36.3 + '@ast-grep/napi-linux-arm64-musl': 0.36.3 + '@ast-grep/napi-linux-x64-gnu': 0.36.3 + '@ast-grep/napi-linux-x64-musl': 0.36.3 + '@ast-grep/napi-win32-arm64-msvc': 0.36.3 + '@ast-grep/napi-win32-ia32-msvc': 0.36.3 + '@ast-grep/napi-win32-x64-msvc': 0.36.3 + '@astrojs/check@0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.6.3)': dependencies: '@astrojs/language-server': 2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.6.3) @@ -8056,6 +8618,8 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.29.7': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -8084,8 +8648,64 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@base-ui/react@1.4.1(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@base-ui/utils': 0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/react-dom': 2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.11 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + optionalDependencies: + '@date-fns/tz': 1.5.0 + '@types/react': 19.1.8 + date-fns: 4.1.0 + + '@base-ui/utils@0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@floating-ui/utils': 0.2.11 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + reselect: 5.2.0 + use-sync-external-store: 1.6.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@braintree/sanitize-url@7.1.2': {} + '@bundled-es-modules/deepmerge@4.3.2': + dependencies: + deepmerge: 4.3.1 + + '@bundled-es-modules/glob@10.4.2': + dependencies: + buffer: 6.0.3 + events: 3.3.0 + glob: 10.5.0 + patch-package: 8.0.1 + path: 0.12.7 + stream: 0.0.3 + string_decoder: 1.3.0 + url: 0.11.4 + + '@bundled-es-modules/memfs@4.17.0(tslib@2.8.1)': + dependencies: + assert: 2.1.0 + buffer: 6.0.3 + events: 3.3.0 + memfs: 4.57.2(tslib@2.8.1) + path: 0.12.7 + stream: 0.0.3 + util: 0.12.5 + transitivePeerDependencies: + - tslib + + '@bundled-es-modules/postcss-calc-ast-parser@0.1.6': + dependencies: + postcss-calc-ast-parser: 0.1.4 + '@capsizecss/unpack@4.0.0': dependencies: fontkitten: 1.0.2 @@ -8375,6 +8995,8 @@ snapshots: '@ctrl/tinycolor@4.2.0': {} + '@date-fns/tz@1.5.0': {} + '@drizzle-team/brocli@0.10.2': {} '@emmetio/abbreviation@2.3.3': @@ -8424,7 +9046,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -8455,7 +9077,7 @@ snapshots: '@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 @@ -8481,7 +9103,7 @@ snapshots: '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 '@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0) @@ -8821,17 +9443,32 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.9 + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + '@floating-ui/dom@1.6.13': dependencies: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/dom': 1.6.13 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@floating-ui/react-dom@2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -8840,6 +9477,8 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tabbable: 6.2.0 + '@floating-ui/utils@0.2.11': {} + '@floating-ui/utils@0.2.9': {} '@fontsource/poppins@5.2.7': {} @@ -8914,7 +9553,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/utils@3.1.3': + '@iconify/utils@3.1.4': dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/types': 2.0.0 @@ -9024,6 +9663,15 @@ snapshots: optionalDependencies: '@types/node': 22.12.0 + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -9229,7 +9877,7 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 @@ -9273,7 +9921,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@mermaid-js/parser@1.1.1': + '@mermaid-js/parser@1.2.0': dependencies: '@chevrotain/types': 11.1.2 @@ -9325,7 +9973,7 @@ snapshots: '@mui/base@5.0.0-beta.62(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@mui/types': 7.2.24(@types/react@19.1.8) '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) @@ -9341,7 +9989,7 @@ snapshots: '@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@mui/core-downloads-tracker': 6.5.0 '@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) '@mui/types': 7.2.24(@types/react@19.1.8) @@ -9362,7 +10010,7 @@ snapshots: '@mui/private-theming@6.4.9(@types/react@19.1.8)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) prop-types: 15.8.1 react: 19.1.0 @@ -9371,7 +10019,7 @@ snapshots: '@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 @@ -9384,7 +10032,7 @@ snapshots: '@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@mui/private-theming': 6.4.9(@types/react@19.1.8)(react@19.1.0) '@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(react@19.1.0) '@mui/types': 7.2.24(@types/react@19.1.8) @@ -9404,7 +10052,7 @@ snapshots: '@mui/utils@6.4.9(@types/react@19.1.8)(react@19.1.0)': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@mui/types': 7.2.24(@types/react@19.1.8) '@types/prop-types': 15.7.14 clsx: 2.1.1 @@ -9510,6 +10158,9 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@pkgjs/parseargs@0.11.0': + optional: true + '@pkgr/core@0.2.9': {} '@popperjs/core@2.11.8': {} @@ -9541,7 +10192,7 @@ snapshots: dependencies: '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 - immer: 11.1.8 + immer: 11.1.15 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.2.0 @@ -9776,17 +10427,6 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.7) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.7) - '@svgr/core@8.1.0(typescript@5.6.3)': - dependencies: - '@babel/core': 7.26.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.6.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - '@svgr/core@8.1.0(typescript@5.9.3)': dependencies: '@babel/core': 7.26.7 @@ -9803,16 +10443,6 @@ snapshots: '@babel/types': 7.29.0 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))': - dependencies: - '@babel/core': 7.26.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.26.7) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': dependencies: '@babel/core': 7.26.7 @@ -9903,6 +10533,8 @@ snapshots: - dayjs - supports-color + '@tabby_ai/hijri-converter@1.0.5': {} + '@temporalio/activity@1.16.0': dependencies: '@temporalio/client': 1.16.0 @@ -9984,7 +10616,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -10002,6 +10634,18 @@ snapshots: '@types/react': 19.1.8 '@types/react-dom': 19.1.1(@types/react@19.1.8) + '@tokens-studio/sd-transforms@1.3.0(style-dictionary@4.4.0(tslib@2.8.1))': + dependencies: + '@bundled-es-modules/deepmerge': 4.3.2 + '@bundled-es-modules/postcss-calc-ast-parser': 0.1.6 + '@tokens-studio/types': 0.5.2 + colorjs.io: 0.5.2 + expr-eval-fork: 2.0.2 + is-mergeable-object: 1.1.1 + style-dictionary: 4.4.0(tslib@2.8.1) + + '@tokens-studio/types@0.5.2': {} + '@trivago/prettier-plugin-sort-imports@6.0.0(prettier@3.8.1)': dependencies: '@babel/generator': 7.28.5 @@ -10110,7 +10754,7 @@ snapshots: '@types/d3-quadtree@3.0.6': {} - '@types/d3-random@3.0.3': {} + '@types/d3-random@3.0.4': {} '@types/d3-scale-chromatic@3.1.0': {} @@ -10161,7 +10805,7 @@ snapshots: '@types/d3-path': 3.1.1 '@types/d3-polygon': 3.0.2 '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 + '@types/d3-random': 3.0.4 '@types/d3-scale': 4.0.9 '@types/d3-scale-chromatic': 3.1.0 '@types/d3-selection': 3.0.11 @@ -10508,6 +11152,19 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 + '@vue/language-core@2.2.0(typescript@5.6.3)': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.33 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.33 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.3 + '@vue/language-core@2.2.0(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.28 @@ -10626,10 +11283,14 @@ snapshots: d3-selection: 3.0.0 d3-zoom: 3.0.0 + '@yarnpkg/lockfile@1.1.0': {} + '@yeskunall/astro-umami@0.0.7(astro@5.18.0(@types/node@22.12.0)(jiti@2.6.1)(rollup@4.57.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.6.3)(yaml@2.8.4))': dependencies: astro: 5.18.0(@types/node@22.12.0)(jiti@2.6.1)(rollup@4.57.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.6.3)(yaml@2.8.4) + '@zip.js/zip.js@2.8.26': {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -10824,6 +11485,14 @@ snapshots: object-is: 1.1.6 util: 0.12.5 + assert@2.1.0: + dependencies: + call-bind: 1.0.8 + is-nan: 1.3.2 + object-is: 1.1.6 + object.assign: 4.1.7 + util: 0.12.5 + assertion-error@2.0.1: {} astring@1.9.0: {} @@ -10977,7 +11646,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -10992,6 +11661,8 @@ snapshots: base64-arraybuffer@1.0.2: optional: true + base64-js@1.5.1: {} + baseline-browser-mapping@2.10.19: {} bcp-47-match@2.0.3: {} @@ -11006,6 +11677,8 @@ snapshots: dependencies: is-windows: 1.0.2 + binary-extensions@2.3.0: {} + body-parser@2.2.0: dependencies: bytes: 3.1.2 @@ -11069,8 +11742,17 @@ snapshots: buffer-from@1.1.2: {} + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + builtin-modules@5.0.0: {} + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bytes@3.1.2: {} cac@6.7.14: {} @@ -11080,6 +11762,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.1 @@ -11092,6 +11779,11 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.7 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase@6.3.0: {} @@ -11131,6 +11823,8 @@ snapshots: chance@1.1.9: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -11170,6 +11864,18 @@ snapshots: undici: 7.24.4 whatwg-mimetype: 4.0.0 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -11182,6 +11888,8 @@ snapshots: chrome-trace-event@1.0.4: {} + ci-info@3.9.0: {} + ci-info@4.2.0: {} ci-info@4.4.0: {} @@ -11239,6 +11947,8 @@ snapshots: colorette@2.0.20: {} + colorjs.io@0.5.2: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -11249,6 +11959,8 @@ snapshots: commander@11.1.0: {} + commander@12.1.0: {} + commander@13.1.0: {} commander@2.20.3: {} @@ -11266,6 +11978,8 @@ snapshots: compare-versions@6.1.1: {} + component-emitter@2.0.0: {} + concat-map@0.0.1: {} concurrently@9.1.2: @@ -11343,15 +12057,6 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.6.3): - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.6.3 - cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.0 @@ -11634,6 +12339,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-jalali@4.1.0-0: {} + date-fns@4.1.0: {} dayjs@1.11.21: {} @@ -11660,12 +12367,23 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -11723,7 +12441,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 csstype: 3.1.3 dom-serializer@2.0.0: @@ -11743,7 +12461,7 @@ snapshots: '@types/trusted-types': 2.0.7 optional: true - dompurify@3.4.11: + dompurify@3.4.12: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -11784,6 +12502,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} electron-to-chromium@1.5.340: {} @@ -11799,6 +12519,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} encoding-sniffer@0.2.1: @@ -12074,6 +12796,10 @@ snapshots: dependencies: eslint: 9.19.0(jiti@2.6.1) + eslint-plugin-react-hooks@5.2.0(eslint@9.19.0(jiti@2.6.1)): + dependencies: + eslint: 9.19.0(jiti@2.6.1) + eslint-plugin-react@7.37.2(eslint@9.19.0(jiti@2.6.1)): dependencies: array-includes: 3.1.8 @@ -12096,6 +12822,28 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.19.0(jiti@2.6.1)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.19.0(jiti@2.6.1) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-tsdoc@0.4.0: dependencies: '@microsoft/tsdoc': 0.15.1 @@ -12253,6 +13001,8 @@ snapshots: expect-type@1.1.0: {} + expr-eval-fork@2.0.2: {} + express@5.1.0: dependencies: accepts: 2.0.0 @@ -12381,6 +13131,10 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + find-yarn-workspace-root@2.0.0: + dependencies: + micromatch: 4.0.8 + flat-cache@4.0.1: dependencies: flatted: 3.3.2 @@ -12402,6 +13156,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.1: dependencies: asynckit: 0.4.0 @@ -12416,6 +13175,12 @@ snapshots: fresh@2.0.0: {} + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + fs-extra@11.3.4: dependencies: graceful-fs: 4.2.11 @@ -12471,6 +13236,19 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} get-proto@1.0.1: @@ -12516,6 +13294,15 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + global-directory@5.0.0: dependencies: ini: 6.0.0 @@ -12848,11 +13635,11 @@ snapshots: i18next-browser-languagedetector@8.0.5: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 i18next@23.16.8: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 i18next@24.2.3(typescript@5.9.3): dependencies: @@ -12868,11 +13655,13 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} immer@10.1.1: {} - immer@11.1.8: {} + immer@11.1.15: {} import-fresh@3.3.0: dependencies: @@ -12887,6 +13676,8 @@ snapshots: indent-string@5.0.0: {} + inherits@2.0.3: {} + inherits@2.0.4: {} ini@6.0.0: {} @@ -12945,6 +13736,10 @@ snapshots: dependencies: has-bigints: 1.1.0 + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-boolean-object@1.2.1: dependencies: call-bound: 1.0.3 @@ -12975,6 +13770,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -13004,12 +13801,16 @@ snapshots: is-hexadecimal@2.0.1: {} + is-in-ssh@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 is-map@2.0.3: {} + is-mergeable-object@1.1.1: {} + is-nan@1.3.2: dependencies: call-bind: 1.0.8 @@ -13075,6 +13876,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -13092,6 +13897,12 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + javascript-natural-sort@0.7.1: {} javascript-obfuscator@4.1.1: @@ -13193,6 +14004,14 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stable-stringify@1.3.0: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + isarray: 2.0.5 + jsonify: 0.0.1 + object-keys: 1.1.1 + json5@2.2.3: {} jsonc-parser@2.3.1: {} @@ -13209,6 +14028,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonify@0.0.1: {} + jspdf@3.0.3: dependencies: '@babel/runtime': 7.27.0 @@ -13237,6 +14058,10 @@ snapshots: khroma@2.1.0: {} + klaw-sync@6.0.0: + dependencies: + graceful-fs: 4.2.11 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -13630,11 +14455,11 @@ snapshots: merge2@1.4.1: {} - mermaid@11.15.0: + mermaid@11.16.0: dependencies: '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.3 - '@mermaid-js/parser': 1.1.1 + '@iconify/utils': 3.1.4 + '@mermaid-js/parser': 1.2.0 '@types/d3': 7.4.3 '@upsetjs/venn.js': 2.0.0 cytoscape: 3.34.0 @@ -13644,7 +14469,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.21 - dompurify: 3.4.11 + dompurify: 3.4.12 es-toolkit: 1.46.1 katex: 0.16.47 khroma: 2.1.0 @@ -14072,6 +14897,13 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + object.fromentries@2.0.8: dependencies: call-bind: 1.0.8 @@ -14114,6 +14946,20 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + opencollective-postinstall@2.0.3: {} optionator@0.8.3: @@ -14184,6 +15030,8 @@ snapshots: p-map@2.1.0: {} + p-map@7.0.4: {} + p-queue@8.1.1: dependencies: eventemitter3: 5.0.1 @@ -14193,6 +15041,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.11: dependencies: quansync: 0.2.11 @@ -14265,6 +15115,23 @@ snapshots: parseurl@1.3.3: {} + patch-package@8.0.1: + dependencies: + '@yarnpkg/lockfile': 1.1.0 + chalk: 4.1.2 + ci-info: 3.9.0 + cross-spawn: 7.0.6 + find-yarn-workspace-root: 2.0.0 + fs-extra: 10.1.0 + json-stable-stringify: 1.3.0 + klaw-sync: 6.0.0 + minimist: 1.2.8 + open: 7.4.2 + semver: 7.7.4 + slash: 2.0.0 + tmp: 0.2.7 + yaml: 2.8.4 + path-browserify@1.0.1: {} path-data-parser@0.1.0: {} @@ -14275,10 +15142,22 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-to-regexp@8.3.0: {} path-type@4.0.0: {} + path-unified@0.2.0: {} + + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + pathe@2.0.3: {} pathval@2.0.0: {} @@ -14325,6 +15204,10 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss-calc-ast-parser@0.1.4: + dependencies: + postcss-value-parser: 3.3.1 + postcss-nested@6.2.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -14340,6 +15223,8 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-value-parser@3.3.1: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -14348,6 +15233,8 @@ snapshots: postgres@3.4.9: {} + powershell-utils@0.1.0: {} + prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -14416,6 +15303,8 @@ snapshots: punycode.js@2.3.1: {} + punycode@1.4.1: {} + punycode@2.3.1: {} qs@6.14.0: @@ -14452,6 +15341,14 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react-day-picker@9.14.0(react@19.1.0): + dependencies: + '@date-fns/tz': 1.5.0 + '@tabby_ai/hijri-converter': 1.0.5 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 19.1.0 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -14507,7 +15404,7 @@ snapshots: dependencies: '@types/use-sync-external-store': 0.0.6 react: 19.1.0 - use-sync-external-store: 1.4.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) optionalDependencies: '@types/react': 19.1.8 redux: 5.0.1 @@ -14552,7 +15449,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -14568,25 +15465,29 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + readdirp@4.1.2: {} readdirp@5.0.0: {} - recharts@3.9.0(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1): + recharts@3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1))(react@19.1.0) clsx: 2.1.1 decimal.js-light: 2.5.1 es-toolkit: 1.46.1 eventemitter3: 5.0.1 - immer: 10.1.1 + immer: 11.1.15 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-is: 19.0.0 react-redux: 9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1) reselect: 5.2.0 tiny-invariant: 1.3.3 - use-sync-external-store: 1.4.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) victory-vendor: 37.3.6 transitivePeerDependencies: - '@types/react' @@ -14782,6 +15683,8 @@ snapshots: dependencies: type-fest: 4.33.0 + remeda@2.39.0: {} + request-light@0.5.8: {} request-light@0.7.0: {} @@ -14849,6 +15752,15 @@ snapshots: robust-predicates@3.0.3: {} + rollup-plugin-visualizer@7.0.1(rollup@4.57.1): + dependencies: + open: 11.0.0 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 18.0.0 + optionalDependencies: + rollup: 4.57.1 + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 @@ -14899,6 +15811,8 @@ snapshots: rrweb-cssom@0.8.0: {} + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -15102,6 +16016,8 @@ snapshots: arg: 5.0.2 sax: 1.4.4 + slash@2.0.0: {} + slash@3.0.0: {} slice-ansi@5.0.0: @@ -15181,6 +16097,10 @@ snapshots: stream-replace-string@2.0.0: {} + stream@0.0.3: + dependencies: + component-emitter: 2.0.0 + string-argv@0.3.2: {} string-template@1.0.0: {} @@ -15191,6 +16111,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.4.0 @@ -15241,6 +16167,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -15268,6 +16198,24 @@ snapshots: strip-json-comments@5.0.1: {} + style-dictionary@4.4.0(tslib@2.8.1): + dependencies: + '@bundled-es-modules/deepmerge': 4.3.2 + '@bundled-es-modules/glob': 10.4.2 + '@bundled-es-modules/memfs': 4.17.0(tslib@2.8.1) + '@zip.js/zip.js': 2.8.26 + chalk: 5.4.1 + change-case: 5.4.4 + commander: 12.1.0 + is-plain-obj: 4.1.0 + json5: 2.2.3 + patch-package: 8.0.1 + path-unified: 0.2.0 + prettier: 3.8.1 + tinycolor2: 1.6.0 + transitivePeerDependencies: + - tslib + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -15379,6 +16327,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.2: {} @@ -15400,6 +16350,8 @@ snapshots: dependencies: tldts-core: 6.1.75 + tmp@0.2.7: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -15645,6 +16597,11 @@ snapshots: dependencies: punycode: 2.3.1 + url@0.11.4: + dependencies: + punycode: 1.4.1 + qs: 6.14.0 + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): dependencies: react: 19.1.0 @@ -15683,8 +16640,16 @@ snapshots: dependencies: react: 19.1.0 + use-sync-external-store@1.6.0(react@19.1.0): + dependencies: + react: 19.1.0 + util-deprecate@1.0.2: {} + util@0.10.4: + dependencies: + inherits: 2.0.3 + util@0.12.5: dependencies: inherits: 2.0.4 @@ -15759,18 +16724,18 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): + vite-plugin-dts@4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): dependencies: '@microsoft/api-extractor': 7.58.7(@types/node@22.12.0) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) '@volar/typescript': 2.4.28 - '@vue/language-core': 2.2.0(typescript@5.9.3) + '@vue/language-core': 2.2.0(typescript@5.6.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 - typescript: 5.9.3 + typescript: 5.6.3 optionalDependencies: vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) transitivePeerDependencies: @@ -15778,16 +16743,40 @@ snapshots: - rollup - supports-color - vite-plugin-svgr@4.3.0(rollup@4.57.1)(typescript@5.6.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): + vite-plugin-dts@4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): dependencies: + '@microsoft/api-extractor': 7.58.7(@types/node@22.12.0) '@rollup/pluginutils': 5.3.0(rollup@4.57.1) - '@svgr/core': 8.1.0(typescript@5.6.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) transitivePeerDependencies: + - '@types/node' - rollup - supports-color - - typescript + + vite-plugin-lib-inject-css@2.2.2(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): + dependencies: + '@ast-grep/napi': 0.36.3 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) + + vite-plugin-static-copy@2.3.2(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): + dependencies: + chokidar: 3.6.0 + fast-glob: 3.3.3 + fs-extra: 11.3.4 + p-map: 7.0.4 + picocolors: 1.1.1 + vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) vite-plugin-svgr@4.3.0(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)): dependencies: @@ -16083,6 +17072,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + wrap-ansi@9.0.0: dependencies: ansi-styles: 6.2.1 @@ -16093,6 +17088,11 @@ snapshots: ws@8.18.0: {} + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} @@ -16191,11 +17191,11 @@ snapshots: immer: 10.1.1 react: 19.1.0 - zustand@5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.4.0(react@19.1.0)): + zustand@5.0.3(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): optionalDependencies: '@types/react': 19.1.8 immer: 10.1.1 react: 19.1.0 - use-sync-external-store: 1.4.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4c6e903e3..acb41cb41 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,7 +3,12 @@ packages: - './packages/*' catalog: + # Pinned to the version the component library was validated against. Base UI + # changed Dialog/Popup transition behaviour after 1.4.x (1.6 broke the modal + # backdrop fade), so bump only after re-checking modal/menu/tooltip transitions. + '@base-ui/react': 1.4.1 '@phosphor-icons/core': ^2.1.1 + '@phosphor-icons/react': ^2.1.7 '@xyflow/react': 12.10.0 # Exact-pinned (no caret): React 19 hard-requires react and react-dom to be # the SAME version — a mismatch throws React error #527. A caret let `react` From fc782c6877a97859ff53df9e3265579af17d0f67 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 14 Aug 2026 11:00:54 +0200 Subject: [PATCH 03/23] Migrate consumers to @workflowbuilder/ui (2/3: consumers) (#45) * refactor: migrate consumers to @workflowbuilder/ui and fix Base UI API breaks * fix(sdk): stop publishing unresolvable ui dependency + leaked types The SDK bundles @workflowbuilder/ui and @base-ui/react into its dist, but declared them as runtime dependencies - so a published @workflowbuilder/sdk would 404 on @workflowbuilder/ui (not published) at npm install. Move both to devDependencies (they are inlined, consumers don't install them). Add @workflowbuilder/ui to the dts plugin's bundledPackages so its types are inlined into dist/index.d.ts instead of leaking unresolvable import('@workflowbuilder/ui') references into the public type surface. Verified: no real ui/base-ui imports remain in dist/index.d.ts. Remove the obsolete docs/overflow-ui.md (described the old external @synergycodes/overflow-ui local-dev flow this migration replaces). * fix(sdk): finish overflow-ui migration for files main added after the branch point Rebasing onto current main pulled in commits landed after this branch diverged (WB-339's use-on-connect.tsx, PR #48's language-selector spec, PR #48's ai-studio undo-redo buttons) that still imported the retired @synergycodes/overflow-ui package. Point them at @workflowbuilder/ui like every other already-migrated call site, and regenerate the lockfile for the fully rebased dependency graph. * fix(sdk): externalize @base-ui/react and @phosphor-icons/react from bundle @workflowbuilder/ui is bundled (not externalized) into the SDK, so @base-ui/react was being inlined transitively into dist/index.js even though the SDK never imports it directly. @phosphor-icons/react was already a direct SDK dependency but was likewise getting bundled a second time on top of the copy demo/ai-studio install directly. Add both to the SDK's Vite external list and move @base-ui/react from devDependencies to dependencies, mirroring the treatment already used for i18next/jsonforms/immer/zustand and matching how packages/ui declares them. * fix(sdk): repoint dead modal CSS selectors at Base UI signal The datepicker z-index fix (index.css) and the variable-suggestions backdrop suppression (variable-text.module.css) both keyed off `.base-Modal-root` and `.mantine-Popover-dropdown`, class names from the retired MUI Base + Mantine modal/popover. Neither selector matched anything after the Base UI migration, silently reintroducing both bugs: a datepicker opened inside a modal rendered behind it, and the variable-suggestions backdrop no longer disappeared when a modal opened on top of it. ModalProvider now toggles a `wb-modal-open` class on whenever the SDK's modal store has a modal open - a stable signal owned by the SDK rather than coupled to @workflowbuilder/ui's internal markup. Both selectors are repointed at it; the datepicker fix additionally keys off Base UI's own `[data-open]` popup attribute, since the DatePicker's popover class is a hashed CSS module class internal to @workflowbuilder/ui. * docs(changeset): clarify Base UI DOM changes and dependency shape Update the move-ui-library-in-repo changeset: @base-ui/react is now a regular dependency of the SDK rather than an inlined implementation detail, and note explicitly that internal DOM structure and class names of the bundled UI changed (MUI Base + Mantine -> Base UI), so consumer styles or tests written against internal class names may need updating. Stays a minor bump. * fix(sdk): declare layer order in module stylesheets using ui.component Three SDK stylesheets open @layer ui.component without declaring the ui.base/ui.component order first. Module-graph order currently loads the declaration earlier by luck; an import reorder or dynamic import would register ui.component first and invert the cascade inside the ui layer - react-day-picker's raw defaults would then beat the calendar theming. Duplicate order statements are no-ops, so the prepends are free. * fix(ai-studio): pin react-is to the react 19 line for recharts Removing the overflow-ui/MUI subtree left react-is resolvable only from stale transitive copies, and pnpm rewired recharts' react-is peer to 17.0.2 (supplied by a vitest transitive). react-is 17 does not know React 19 element symbols, so isFragment() is always false and fragment children inside charts would be silently dropped. Pinned in the catalog next to react/react-dom (must move in lockstep) and declared by ai-studio to steer the peer resolution. * docs(sdk): shorten the modal-backdrop suppression comment * fix(sdk): keep the modal dialog mounted so enter/exit transitions run ModalProvider mounted Dialog.Root conditionally: it appeared with open already true and unmounted on close, so Base UI's transition lifecycle (data-starting-style / data-ending-style) never ran in either direction - on every Base UI version. The root now stays mounted with open driving it, and the last modal content is retained through the exit transition because the store clears it on close. * build: bump @base-ui/react to the 1.7 line The 1.4.1 pin guarded against a modal-fade regression attributed to Base UI 1.6; the fade was actually broken by the SDK's own conditional Dialog mounting (fixed in the previous commit) on every version. Caret range in the catalog restores dependency dedupe for npm consumers of the published packages; the workspace stays locked to an exact version via the lockfile. * perf(sdk): stop bundling @workflowbuilder/ui CSS twice dist/style.css carried every ui rule twice: once through the explicit @import of ui's index.css and once through the JS module graph (each bundled ui chunk carries a libInjectCss-injected CSS import). The JS graph vector stays - it also tree-shakes CSS of components the SDK does not use; the redundant @import goes. Verified by marker counts halving in the rebuilt bundle (typography 24 -> 12) and a visual pass on demo. * docs: note that app builds consume the prebuilt packages/ui dist * docs(sdk): extend the archival banner over the UI-library swap and fix token paths The decision log's sections 3-4 describe the overflow-ui Vite alias and import layout as current; the banner now covers the swap to the in-repo library and corrects the disproven CSS-extraction claim. The token doc pointed readers into a node_modules symlink instead of packages/ui/dist. * docs(changeset): cover derived type shapes and the restored modal fade * fix(deps): resolve app react from the catalog A caret react next to catalog react-dom is the exact drift the catalog comment warns about - React 19 requires the pair to match (React error 527). * fix(sdk): layer the xyflow stylesheet and drop its duplicate JS import xyflow CSS arrived through two vectors (a bare css @import and a JS-side import in diagram.tsx), both unlayered. Once the ui library's port rules moved into @layer ui.component, unlayered xyflow beat them and node ports rendered as raw 6px dark dots. The stylesheet now joins the declared (and previously unused) ext-lib layer, which the ui layer outranks; the duplicate JS import goes away. Verified in demo: styled ports and the node-as-port connection target both render from the layered rules. * fix(sdk): declare a single top-level cascade layer in the stylesheet UI chunk CSS arrives through the JS module graph and can parse before index.css; its stamped statement then registered top-level ui first and the SDK's reset/ext-lib/ui declaration could only append, flipping the effective order to [ui, reset, ext-lib] - xyflow beat every component style. Now every stylesheet opens with the identical @layer ui.base, ui.component; statement, xyflow and the SDK resets join ui.base, and no second top-level layer exists to race against. Verified live in demo: only one order statement registers, xyflow's handle defaults sit in ui.base and lose to ui.component, and an unlayered :root override prepended before all component CSS still wins. * fix(sdk): gate the modal portal out of server-side rendering The always-mounted portal evaluated createPortal(..., document.body) on every render, so SSR of a closed editor threw ReferenceError. * fix(sdk): match the date-picker trigger override to the new markup DatePicker renders Popover.Trigger as the button itself, so the .date-picker > div > button override matched nothing and date/datetime fields lost their 2.5rem trigger height. * docs(ui): align base-ui claims with the ^1.7.0 dependency README and CHANGELOG promised a validated 1.4.x pin and blamed later versions for transition regressions; the regression was our conditional mounting, and the actual dependency is ^1.7.0. Changeset records the dependency-contract change. * docs(sdk): drop dead promises and add an npm path to the token guide README advertised a scroll-thumb-hover token that does not exist and claimed the html/body/root 100vh sizing ships in the package. The token guide only described monorepo paths and suggested editing generated dist CSS. Both now describe what actually ships and how an npm consumer overrides tokens. * fix(sdk): drop the global popover z-index override The rule flattened every open Base UI popup to 1002 while a modal was open - including Select and Menu, which sit at 10000 - so the variable suggestions panel at 3000 painted over an open dropdown. It also used !important against the README's own contract and matched consumer popups. The date-picker positioner now carries the shared popup class in @workflowbuilder/ui, which is what the rule was working around; the body class stays for the variable-suggestions backdrop. * build(deps): pin base-ui to an exact 1.7.0 The keep-mounted modal depends on Base UI's transition lifecycle, and the range ships to consumers - a caret would let a minor change it without our test. * refactor(sdk): trim inline comments to the traps worth keeping * refactor(sdk): drop dead rules from the dynamic typed input stylesheet The stylesheet declared .container--select twice; merge the two into one rule and drop its > select child, which never matched since the component renders the UI library Select. Remove the unused .cursor-pointer and .reset-button classes, and a comment explaining why .date-picker needs no descendant selector. --------- Co-authored-by: Jan Librowski Co-authored-by: librowski-synergy Co-authored-by: librowski-synergy --- .changeset/move-ui-library-in-repo.md | 7 + .changeset/sdk-date-picker-trigger-height.md | 5 + .changeset/sdk-single-top-layer.md | 5 + .changeset/sdk-ssr-modal-portal.md | 5 + .changeset/ui-base-ui-1-7.md | 5 + CLAUDE.md | 2 + apps/ai-studio/package.json | 8 +- .../controls/ai-studio-controls.tsx | 2 +- .../buttons-undo-redo/buttons-undo-redo.tsx | 2 +- apps/ai-studio/vite.config.mts | 25 - apps/backend/README.md | 1 - apps/demo/package.json | 7 +- .../multi-port-node-template.tsx | 2 +- .../buttons-undo-redo/buttons-undo-redo.tsx | 2 +- apps/demo/vite.config.mts | 24 - .../src/content/docs/get-started/theming.md | 2 +- .../content/docs/guides/add-a-custom-node.mdx | 4 +- .../content/docs/node-schemas/form-layouts.md | 10 +- docs/how-to-change-css-tokens.md | 39 +- docs/overflow-ui.md | 23 - package.json | 1 - packages/sdk/DECISION-LOG.md | 8 +- packages/sdk/README.md | 18 +- packages/sdk/package.json | 3 +- .../button-submit/button-submit.tsx | 2 +- .../form-control-with-label.module.css | 2 + .../form-control-with-label.tsx | 2 +- .../components/form/label/label.module.css | 2 + .../sdk/src/components/form/label/label.tsx | 2 +- .../src/components/sidebar/sidebar.module.css | 2 + .../sdk/src/components/sidebar/sidebar.tsx | 2 +- .../app-bar/components/controls/controls.tsx | 2 +- .../project-selection.spec.tsx | 6 +- .../project-selection/project-selection.tsx | 2 +- .../toggle-dark-mode/toggle-dark-mode.tsx | 2 +- .../toggle-read-only-mode.tsx | 2 +- .../functions/get-controls-dots-items.tsx | 2 +- packages/sdk/src/features/diagram/diagram.tsx | 1 - .../edge-label-renderer.tsx | 4 +- .../edges/label-edge/use-label-edge-hover.ts | 2 +- .../self-connecting-edge.tsx | 2 +- .../edges/temporary-edge/temporary-edge.tsx | 2 +- .../diagram/handles/get-handles-alignment.ts | 8 +- .../diagram/hooks/use-on-connect.spec.tsx | 4 +- .../features/diagram/hooks/use-on-connect.tsx | 2 +- .../ai-agent-node-template.tsx | 2 +- .../diagram/nodes/ai-node-container.tsx | 2 +- .../placeholder-button/placeholder-button.tsx | 2 +- .../diagram/nodes/decision-node-container.tsx | 2 +- .../decision-node-template.tsx | 2 +- .../features/diagram/nodes/node-container.tsx | 2 +- .../diagram/nodes/start-node-container.tsx | 2 +- .../start-node-template.tsx | 2 +- .../workflow-node-template.tsx | 2 +- .../language-selector.spec.tsx | 2 +- .../language-selector/language-selector.tsx | 2 +- .../export-modal/export-modal.tsx | 2 +- .../import-modal/import-modal.tsx | 2 +- .../components/save-button/save-button.tsx | 2 +- .../stores/use-integration-store.spec.ts | 2 +- .../stores/use-integration-store.ts | 2 +- .../integration/utils/show-snackbar.ts | 2 +- .../ai-tools-control/ai-tools-control.tsx | 2 +- .../add-ai-tool-footer/add-ai-tool-footer.tsx | 2 +- .../add-ai-tool-form-content.tsx | 2 +- .../date-picker-control.tsx | 2 +- .../branch-card/branch-card.tsx | 2 +- .../condition-modal-footer.tsx | 2 +- .../dynamic-conditions-control.tsx | 2 +- .../conditions-form-field.tsx | 2 +- .../conditions-form.tsx | 2 +- .../select-control/select-control.tsx | 2 +- .../switch-control/switch-control.tsx | 4 +- .../text-area-control/text-area-control.tsx | 2 +- .../controls/text-control/text-control.tsx | 2 +- .../accordion-layout/accordion-layout.tsx | 2 +- .../delete-confirmation.tsx | 2 +- .../modals/providers/modal-provider.tsx | 49 +- .../features/modals/stores/use-modal-store.ts | 2 +- .../components/footer/palette-footer.tsx | 2 +- .../components/header/palette-header.tsx | 2 +- .../components/items/palette-items.tsx | 2 +- .../edge-properties/edge-properties.tsx | 2 +- .../header/properties-bar-header.tsx | 2 +- .../properties-bar/properties-bar.tsx | 2 +- .../features/snackbar/snackbar-container.tsx | 2 +- .../components/syntax-highlighter-lazy.tsx | 2 +- .../dynamic-typed-input/constants.ts | 2 +- .../dynamic-typed-input.module.css | 25 +- .../dynamic-typed-input.tsx | 6 +- .../dynamic-typed-variable-or-input.tsx | 2 +- .../variable-text/variable-text.module.css | 3 +- .../variable-text/variable-text.tsx | 2 +- .../pane-list/pane-list.tsx | 2 +- .../variable-form/variable-form.tsx | 2 +- .../variable-preview/variable-preview.tsx | 2 +- .../variables/modals/tab/tab-header.tsx | 2 +- .../use-workflow-builder-actions.spec.tsx | 2 +- packages/sdk/src/index.css | 63 +- packages/sdk/src/index.ts | 2 +- packages/sdk/src/types/controls.ts | 2 +- packages/sdk/src/types/labels.ts | 2 +- packages/sdk/src/utils/show-snackbar.tsx | 2 +- .../workflow-builder-root.spec.tsx | 2 +- packages/sdk/vite.config.mts | 30 +- packages/ui/CHANGELOG.md | 2 +- packages/ui/README.md | 11 +- packages/ui/css-layers.md | 12 + pnpm-lock.yaml | 796 ++---------------- pnpm-workspace.yaml | 10 +- 110 files changed, 386 insertions(+), 996 deletions(-) create mode 100644 .changeset/move-ui-library-in-repo.md create mode 100644 .changeset/sdk-date-picker-trigger-height.md create mode 100644 .changeset/sdk-single-top-layer.md create mode 100644 .changeset/sdk-ssr-modal-portal.md create mode 100644 .changeset/ui-base-ui-1-7.md delete mode 100644 docs/overflow-ui.md diff --git a/.changeset/move-ui-library-in-repo.md b/.changeset/move-ui-library-in-repo.md new file mode 100644 index 000000000..b8dd6b002 --- /dev/null +++ b/.changeset/move-ui-library-in-repo.md @@ -0,0 +1,7 @@ +--- +'@workflowbuilder/sdk': minor +--- + +Consume the UI component library from the in-repo `@workflowbuilder/ui` (Base UI) instead of the published `@synergycodes/overflow-ui`. + +The SDK previously bundled `@synergycodes/overflow-ui@1.0.0-beta.27` (built on MUI / Mantine / Emotion / Floating UI). It now bundles the in-repo `@workflowbuilder/ui@2.0.0`, rebuilt on [Base UI](https://base-ui.com/). `@base-ui/react` is now a regular dependency of the SDK (installed automatically, not bundled) rather than an inlined implementation detail. Bundled component visuals and interaction details change accordingly; the SDK's exported symbols are unchanged, but public types deriving from the UI library (`InputControlProps`, `TextAreaControlProps`) now build on `@workflowbuilder/ui` type shapes (picked keys unchanged), and the internal DOM structure and class names of all bundled UI changed (MUI Base + Mantine → Base UI) — styles or tests written against those internal class names may need updating. Modal open/close now runs its enter and exit fade transitions (previously the dialog appeared and disappeared instantly). diff --git a/.changeset/sdk-date-picker-trigger-height.md b/.changeset/sdk-date-picker-trigger-height.md new file mode 100644 index 000000000..8d95131a5 --- /dev/null +++ b/.changeset/sdk-date-picker-trigger-height.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/sdk': patch +--- + +Date and datetime variable inputs regained their intended `2.5rem` trigger height and left-aligned text; the style override now matches the new DatePicker markup. diff --git a/.changeset/sdk-single-top-layer.md b/.changeset/sdk-single-top-layer.md new file mode 100644 index 000000000..21f883139 --- /dev/null +++ b/.changeset/sdk-single-top-layer.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/sdk': minor +--- + +The SDK stylesheet now declares a single top-level cascade layer: XYFlow's stylesheet and the SDK resets moved from the `ext-lib` / `reset` layers into `ui.base`, and the file opens with the same `@layer ui.base, ui.component;` statement as every `@workflowbuilder/ui` stylesheet. Component styling no longer depends on stylesheet load order. If you targeted the removed `reset` / `ext-lib` layer names, plain unlayered CSS wins over all library layers. diff --git a/.changeset/sdk-ssr-modal-portal.md b/.changeset/sdk-ssr-modal-portal.md new file mode 100644 index 000000000..1dd301c26 --- /dev/null +++ b/.changeset/sdk-ssr-modal-portal.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/sdk': patch +--- + +`ModalProvider` no longer touches `document` during server-side rendering; the modal portal mounts after hydration. Fixes `ReferenceError: document is not defined` when the editor renders in SSR frameworks such as Next.js. diff --git a/.changeset/ui-base-ui-1-7.md b/.changeset/ui-base-ui-1-7.md new file mode 100644 index 000000000..9f26fb365 --- /dev/null +++ b/.changeset/ui-base-ui-1-7.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +`@base-ui/react` dependency moved from `1.4.1` to `1.7.0` (still exact-pinned). Overlay transitions (Modal, Menu, Select, Tooltip, DatePicker) were re-validated on the 1.7 line. diff --git a/CLAUDE.md b/CLAUDE.md index 6788454bb..b06383bb2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,8 @@ Three onboarding paths (A installs from npm; B, C run the repo locally). README Path B is UI-only and does not need Docker. Path C requires `pnpm infra:up` before backend/worker can start; the backend applies pending migrations automatically at boot. +App builds and dev servers consume the prebuilt `packages/ui/dist` (created on install). After editing `packages/ui/src`, run `pnpm build:ui` (or keep `pnpm --filter @workflowbuilder/ui dev` watching) - app commands do not rebuild it. + ### Agent signals Long-running processes already emit stable log lines that scripts and agents can grep for: diff --git a/apps/ai-studio/package.json b/apps/ai-studio/package.json index 747ed5079..ed9adb2bb 100644 --- a/apps/ai-studio/package.json +++ b/apps/ai-studio/package.json @@ -13,15 +13,19 @@ "test:watch": "vitest --passWithNoTests" }, "dependencies": { + "@base-ui/react": "catalog:", + "@jsonforms/core": "^3.4.1", + "@jsonforms/react": "^3.4.1", "@phosphor-icons/react": "^2.1.7", - "@synergycodes/overflow-ui": "1.0.0-beta.27", "@workflow-builder/types": "workspace:*", + "@workflowbuilder/ui": "workspace:*", "@xyflow/react": "catalog:", "clsx": "^2.1.1", "html-to-image": "1.11.11", "immer": "^10.1.1", "mermaid": "^11.15.0", - "react": "^19.1.0", + "react": "catalog:", + "react-is": "catalog:", "react-dom": "catalog:", "react-i18next": "^15.4.1", "react-markdown": "^10.1.0", diff --git a/apps/ai-studio/src/components/controls/ai-studio-controls.tsx b/apps/ai-studio/src/components/controls/ai-studio-controls.tsx index 2e93bae8e..be95533b7 100644 --- a/apps/ai-studio/src/components/controls/ai-studio-controls.tsx +++ b/apps/ai-studio/src/components/controls/ai-studio-controls.tsx @@ -1,5 +1,5 @@ -import { NavButton } from '@synergycodes/overflow-ui'; import { Icon, getStoreEdges, getStoreNodes } from '@workflowbuilder/sdk'; +import { NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback } from 'react'; diff --git a/apps/ai-studio/src/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx b/apps/ai-studio/src/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx index c12f452fc..7472ffbca 100644 --- a/apps/ai-studio/src/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx +++ b/apps/ai-studio/src/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx @@ -1,5 +1,5 @@ -import { NavButton } from '@synergycodes/overflow-ui'; import { Icon, useStore } from '@workflowbuilder/sdk'; +import { NavButton } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; import { redo, undo, useUndoRedoStore } from '../../stores/use-undo-redo-store'; diff --git a/apps/ai-studio/vite.config.mts b/apps/ai-studio/vite.config.mts index f1e72892e..d2b9039ac 100644 --- a/apps/ai-studio/vite.config.mts +++ b/apps/ai-studio/vite.config.mts @@ -5,20 +5,12 @@ import { defineConfig } from 'vite'; import svgr from 'vite-plugin-svgr'; export default defineConfig(() => { - const shouldUseLocalOverflowUI = process.env.LOCAL_OVERFLOW_UI === 'true'; - const sdkDirectory = path.resolve(import.meta.dirname, '../../packages/sdk'); return { plugins: [svgr(), react()], resolve: { alias: [ - ...(shouldUseLocalOverflowUI - ? Object.entries(getLocalOverflowUIAliases()).map(([find, replacement]) => ({ - find, - replacement, - })) - : []), { find: /^@workflowbuilder\/sdk\/style\.css$/, replacement: path.resolve(sdkDirectory, 'src/index.css'), @@ -27,14 +19,6 @@ export default defineConfig(() => { find: /^@workflowbuilder\/sdk$/, replacement: path.resolve(sdkDirectory, 'src/index.ts'), }, - // overflow-ui doesn't expose ./dist/index.css via package.json exports - { - find: 'overflow-ui-css', - replacement: path.resolve( - sdkDirectory, - 'node_modules/@synergycodes/overflow-ui/dist/index.css', - ), - }, // SDK source files use @/ to refer to their own root (packages/sdk/src/...). // AI Studio files do not use @/ (they import via @workflowbuilder/sdk subpaths), // so it's safe to point @/ at the SDK root for cross-package alias parity. @@ -56,12 +40,3 @@ export default defineConfig(() => { }, }; }); - -function getLocalOverflowUIAliases(): Record { - const distribution = path.resolve(import.meta.dirname, '../../../overflow-ui/packages/ui/dist'); - - return { - '@synergycodes/overflow-ui/tokens.css': path.join(distribution, 'tokens.css'), - '@synergycodes/overflow-ui': path.join(distribution, 'overflow-ui.js'), - }; -} diff --git a/apps/backend/README.md b/apps/backend/README.md index 018180252..f6c03f0e7 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -79,7 +79,6 @@ All scripts run from the monorepo root. Grouped by purpose: | `dev:backend` | Backend only (Hono server with `tsx watch`) | | `dev:worker` | Execution worker only (Temporal worker with `tsx watch`) | | `dev:docs` | Docs site (Astro) | -| `dev:local` | Demo frontend with `LOCAL_OVERFLOW_UI=true` (linked local overflow-ui) | ### Infra (Docker lifecycle) diff --git a/apps/demo/package.json b/apps/demo/package.json index 4869d045b..9187d9d3e 100644 --- a/apps/demo/package.json +++ b/apps/demo/package.json @@ -16,9 +16,12 @@ "test:watch": "vitest" }, "dependencies": { + "@base-ui/react": "catalog:", + "@jsonforms/core": "^3.4.1", + "@jsonforms/react": "^3.4.1", "@microsoft/clarity": "^1.0.0", "@phosphor-icons/react": "^2.1.7", - "@synergycodes/overflow-ui": "1.0.0-beta.27", + "@workflowbuilder/ui": "workspace:*", "@xyflow/react": "catalog:", "ajv": "catalog:", "clsx": "^2.1.1", @@ -28,7 +31,7 @@ "immer": "^10.1.1", "jspdf": "^3.0.1", "libavoid-js": "0.4.0-beta.1", - "react": "^19.1.0", + "react": "catalog:", "react-dom": "catalog:", "react-i18next": "^15.4.1", "remeda": "^2.19.2", diff --git a/apps/demo/src/app/components/multi-port-node/multi-port-node-template.tsx b/apps/demo/src/app/components/multi-port-node/multi-port-node-template.tsx index 96b090494..05fb08080 100644 --- a/apps/demo/src/app/components/multi-port-node/multi-port-node-template.tsx +++ b/apps/demo/src/app/components/multi-port-node/multi-port-node-template.tsx @@ -1,6 +1,6 @@ -import { NodeDescription, NodeIcon, NodePanel } from '@synergycodes/overflow-ui'; import { Icon, defineNodeTemplate, getHandleId, statusOptions } from '@workflowbuilder/sdk'; import type { NodeDataProperties, WorkflowNodeTemplateProps } from '@workflowbuilder/sdk'; +import { NodeDescription, NodeIcon, NodePanel } from '@workflowbuilder/ui'; import { Handle, Position } from '@xyflow/react'; import clsx from 'clsx'; import { memo, useMemo } from 'react'; diff --git a/apps/demo/src/app/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx b/apps/demo/src/app/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx index c12f452fc..7472ffbca 100644 --- a/apps/demo/src/app/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx +++ b/apps/demo/src/app/plugins/undo-redo/components/buttons-undo-redo/buttons-undo-redo.tsx @@ -1,5 +1,5 @@ -import { NavButton } from '@synergycodes/overflow-ui'; import { Icon, useStore } from '@workflowbuilder/sdk'; +import { NavButton } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; import { redo, undo, useUndoRedoStore } from '../../stores/use-undo-redo-store'; diff --git a/apps/demo/vite.config.mts b/apps/demo/vite.config.mts index f7a6a5b55..2f1aa453a 100644 --- a/apps/demo/vite.config.mts +++ b/apps/demo/vite.config.mts @@ -16,7 +16,6 @@ import { export default defineConfig(({ mode }) => { const isProduction = mode === 'production'; const isAnalyze = process.env.ANALYZE === 'true'; - const shouldUseLocalOverflowUI = process.env.LOCAL_OVERFLOW_UI === 'true'; const plugins: PluginOption[] = []; @@ -52,12 +51,6 @@ export default defineConfig(({ mode }) => { plugins, resolve: { alias: [ - ...(shouldUseLocalOverflowUI - ? Object.entries(getLocalOverflowUIAliases()).map(([find, replacement]) => ({ - find, - replacement, - })) - : []), { find: /^@workflowbuilder\/sdk\/style\.css$/, replacement: path.resolve(sdkDirectory, 'src/index.css'), @@ -66,14 +59,6 @@ export default defineConfig(({ mode }) => { find: /^@workflowbuilder\/sdk$/, replacement: path.resolve(sdkDirectory, 'src/index.ts'), }, - // overflow-ui doesn't expose ./dist/index.css via package.json exports - { - find: 'overflow-ui-css', - replacement: path.resolve( - sdkDirectory, - 'node_modules/@synergycodes/overflow-ui/dist/index.css', - ), - }, // SDK source files use @/ to refer to their own root (packages/sdk/src/...). // Demo files do not use @/ (they import via @workflowbuilder/sdk subpaths), // so it's safe to point @/ at the SDK root for cross-package alias parity with sdk's vite.config. @@ -96,15 +81,6 @@ export default defineConfig(({ mode }) => { }; }); -function getLocalOverflowUIAliases(): Record { - const distribution = path.resolve(import.meta.dirname, '../../../overflow-ui/packages/ui/dist'); - - return { - '@synergycodes/overflow-ui/tokens.css': path.join(distribution, 'tokens.css'), - '@synergycodes/overflow-ui': path.join(distribution, 'overflow-ui.js'), - }; -} - type EnvMode = 'demo' | 'production' | 'development' | 'staging'; const fileReplacementsMap: Record = { demo: [], diff --git a/apps/docs/src/content/docs/get-started/theming.md b/apps/docs/src/content/docs/get-started/theming.md index 9838b9462..85527f326 100644 --- a/apps/docs/src/content/docs/get-started/theming.md +++ b/apps/docs/src/content/docs/get-started/theming.md @@ -23,4 +23,4 @@ Provide the font yourself (via `@font-face`, `@fontsource/`, etc.) — the ## Other tokens -The SDK exposes a small surface of `--wb-*` variables (background, scrollbar, transitions) plus the larger `--ax-*` design-token set re-exported from `@synergycodes/overflow-ui`. See [Design System & Customization](/overview/features/design-system-and-customization/) for the full token map. +The SDK exposes a small surface of `--wb-*` variables (background, scrollbar, transitions) plus the larger `--ax-*` design-token set re-exported from `@workflowbuilder/ui`. See [Design System & Customization](/overview/features/design-system-and-customization/) for the full token map. diff --git a/apps/docs/src/content/docs/guides/add-a-custom-node.mdx b/apps/docs/src/content/docs/guides/add-a-custom-node.mdx index 8dd8d34e9..6a3977f07 100644 --- a/apps/docs/src/content/docs/guides/add-a-custom-node.mdx +++ b/apps/docs/src/content/docs/guides/add-a-custom-node.mdx @@ -197,9 +197,9 @@ The built-in renderer gives every node a header and one input + one output handl `my-node-template.tsx`: ```tsx -import { NodeDescription, NodeIcon, NodePanel } from '@synergycodes/overflow-ui'; import { Icon, getHandleId } from '@workflowbuilder/sdk'; import type { WorkflowNodeTemplateProps } from '@workflowbuilder/sdk'; +import { NodeDescription, NodeIcon, NodePanel } from '@workflowbuilder/ui'; import { Handle, Position } from '@xyflow/react'; import { memo, useMemo } from 'react'; @@ -230,7 +230,7 @@ export const MyNodeTemplate = memo( ); ``` -The example composes the node from `@synergycodes/overflow-ui` primitives (`NodePanel.Root`, `NodePanel.Header`, `NodePanel.Handles`) — the same building blocks Workflow Builder uses for its own node renderers, so the result matches the editor's visual language out of the box. +The example composes the node from `@workflowbuilder/ui` primitives (`NodePanel.Root`, `NodePanel.Header`, `NodePanel.Handles`) — the same building blocks Workflow Builder uses for its own node renderers, so the result matches the editor's visual language out of the box. The component receives [`WorkflowNodeTemplateProps`](/api/components/workflownodetemplateprops/). Use [`getHandleId`](/api/utilities/gethandleid/) for handle IDs and pass `innerId` when a node has more than one handle of the same type. If your template needs typed access to `data.properties`, wrap the component in [`defineNodeTemplate`](/api/components/definenodetemplate/) to bind a schema-derived properties type. diff --git a/apps/docs/src/content/docs/node-schemas/form-layouts.md b/apps/docs/src/content/docs/node-schemas/form-layouts.md index 2ee21f252..ad01e332c 100644 --- a/apps/docs/src/content/docs/node-schemas/form-layouts.md +++ b/apps/docs/src/content/docs/node-schemas/form-layouts.md @@ -97,11 +97,11 @@ Text-only elements that don't bind to a property. Use them when the property pan Plain text label — uses the editor's default body styling. -| Prop | Type | Required | Notes | -| ---------- | -------- | -------- | ---------------------------------------------------------------------------------------------- | -| `text` | string | yes | The label text. | -| `required` | boolean | no | Append a `*` to indicate the following section contains required fields. | -| `size` | ItemSize | no | Visual size — `'small'`, `'medium'` (default), `'large'`. Type re-exported from `overflow-ui`. | +| Prop | Type | Required | Notes | +| ---------- | -------- | -------- | ------------------------------------------------------------------------------------------------------ | +| `text` | string | yes | The label text. | +| `required` | boolean | no | Append a `*` to indicate the following section contains required fields. | +| `size` | ItemSize | no | Visual size — `'small'`, `'medium'` (default), `'large'`. Type re-exported from `@workflowbuilder/ui`. | ```ts { type: 'Label', text: 'Connection details' } diff --git a/docs/how-to-change-css-tokens.md b/docs/how-to-change-css-tokens.md index 247acf6da..19b57567d 100644 --- a/docs/how-to-change-css-tokens.md +++ b/docs/how-to-change-css-tokens.md @@ -1,7 +1,38 @@ -# How to change css tokens? +# How to change CSS tokens? -You can use our enterprise version with available figma design kit. We store tokens (colors) in `packages/sdk/src/index.css`, you'll find a CSS import: `@import '@synergycodes/overflow-ui/tokens.css';` with our figma you replace that import with generated from figma desing kit. +All colors, spacing, and radii resolve through `--ax-*` custom properties +defined in `@workflowbuilder/ui/tokens.css` (imported by the SDK's stylesheet). +The shipped defaults live inside the `ui.base` cascade layer, so a plain +unlayered override in your own stylesheet always wins - import order does not +matter. -Alternatively, if you want to stay with community version and do not pay for the licenses, you can look inside `node_modules` at `packages/sdk/node_modules/@synergycodes/overflow-ui/dist/tokens.css`, check the variable names, and manually set some or all of the colors in your repository to overwrite them. +## Consuming from npm (SDK or UI package) -`@synergycodes/overflow-ui` is our library for UI elements [overflow-ui](https://github.com/synergycodes/overflow-ui) +Override the variables you care about in any stylesheet of your app: + +```css +:root { + --ax-button-primary-bg-default: #0f62fe; + --ax-ui-bg-tertiary-default: #f4f4f4; +} +``` + +To discover variable names, inspect elements in devtools or browse +`node_modules/@workflowbuilder/ui/dist/tokens.css`. Theme-specific values are +keyed on `html[data-theme='light' | 'dark']`, so scope your overrides the same +way when they should apply to one theme only. + +## Working in this monorepo + +The source of truth is `packages/tokens/tokens.json` (a Figma Tokens Studio +export) plus the build in `packages/tokens/src`. Edit the export (or re-export +from Figma) and run `pnpm build:ui`. + +Do **not** edit `packages/ui/dist/tokens.css` - it is generated output and the +next build overwrites it. + +## Enterprise + +The enterprise version ships a Figma design kit; its generated stylesheet +replaces the `@import '@workflowbuilder/ui/tokens.css';` line in +`packages/sdk/src/index.css`. diff --git a/docs/overflow-ui.md b/docs/overflow-ui.md deleted file mode 100644 index d240c9344..000000000 --- a/docs/overflow-ui.md +++ /dev/null @@ -1,23 +0,0 @@ -# overflow-ui - -Overflow-ui is an open-to-the-public UI library developed by Synergy Codes: - -https://www.npmjs.com/package/@synergycodes/overflow-ui - -https://github.com/synergycodes/overflow-ui - -## How can I work locally on both `workflow-builder` and `overflow-ui`? - -Both repositories must be cloned next to each other in the same parent directory: - -``` -some-directory/ - overflow-ui/ - workflow-builder/ -``` - -1. Build the dist files in the overflow-ui tokens package: `pnpm tokens prepare`. -2. Build the `overflow-ui` dist: `pnpm ui build`. -3. In this repository, start the frontend with: `pnpm dev:local` - -The `dev:local` script sets a `LOCAL_OVERFLOW_UI=true` flag that makes Vite resolve `@synergycodes/overflow-ui` directly from the local `../overflow-ui/packages/ui/dist/` instead of from npm. No manual changes to `package.json` or CSS imports are needed. diff --git a/package.json b/package.json index 92a18d43d..150767360 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "dev:backend": "pnpm --filter backend dev", "dev:worker": "pnpm --filter execution-worker dev", "dev:docs": "pnpm --filter @workflow-builder/docs dev", - "dev:local": "LOCAL_OVERFLOW_UI=true pnpm --filter @workflow-builder/demo dev", "infra:up": "docker compose -f apps/backend/docker-compose.yml up -d", "infra:down": "docker compose -f apps/backend/docker-compose.yml down", "infra:wait": "node tools/wait-for-temporal.mjs", diff --git a/packages/sdk/DECISION-LOG.md b/packages/sdk/DECISION-LOG.md index 297eb09ec..167826c13 100644 --- a/packages/sdk/DECISION-LOG.md +++ b/packages/sdk/DECISION-LOG.md @@ -11,7 +11,13 @@ > ``. References below to `Editor`, `createWorkflowBuilder`, > `WorkflowBuilderConfig`, `WorkflowBuilderInstance` describe symbols that no > longer exist; see current SDK exports in `packages/sdk/src/index.ts` for the -> post-refactor surface. The notes are retained for historical context only. +> post-refactor surface. The UI library was later swapped from the npm package +> `@synergycodes/overflow-ui` to the in-repo `@workflowbuilder/ui`: the +> `overflow-ui-css` Vite alias and the `@import` layout in sections 3-4 no +> longer exist, and the "Vite doesn't extract CSS from bundled node_modules" +> claim in section 3 turned out to be false (bundled chunk CSS imports ARE +> extracted - the SDK now relies on exactly that). The notes are retained for +> historical context only. ## Context diff --git a/packages/sdk/README.md b/packages/sdk/README.md index d5bca918e..5af2dfc61 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -210,9 +210,9 @@ The editor exposes a small set of CSS custom properties for top-level styling. O } ``` -Available `--wb-*` tokens: `--wb-background-color`, `--wb-font-family`, `--wb-transition`, plus scrollbar styling (`--wb-scroll-width`, `--wb-scroll-radius`, `--wb-scroll-thumb-color`, `--wb-scroll-thumb-hover-color`, `--wb-scroll-track-color`). +Available `--wb-*` tokens: `--wb-background-color`, `--wb-font-family`, `--wb-transition`, plus scrollbar styling (`--wb-scroll-width`, `--wb-scroll-radius`, `--wb-scroll-thumb-color`, `--wb-scroll-track-color`). -Deeper color and spacing customization (palette, semantic UI tokens) goes through the `--ax-*` token layer from `@synergycodes/overflow-ui`. Full guide: [Design system and customization](https://www.workflowbuilder.io/docs/overview/features/design-system-and-customization/). +Deeper color and spacing customization (palette, semantic UI tokens) goes through the `--ax-*` token layer from `@workflowbuilder/ui`. Full guide: [Design system and customization](https://www.workflowbuilder.io/docs/overview/features/design-system-and-customization/). ## CSS — global resets @@ -224,7 +224,12 @@ body { background-color: var(--wb-background-color); overflow: hidden; } +``` + +The stylesheet does **not** size your document — the editor fills whatever container +you give it, so give it one. A typical full-viewport host adds in its own CSS: +```css html, body, #root { @@ -232,8 +237,8 @@ body, } ``` -The rules ship inside `@layer reset` (the lowest precedence in the SDK's cascade layer -order), so any consumer-level rule on `body` / `html` / `#root` wins automatically — no +The shipped resets live inside `@layer ui.base` (the lowest layer in the cascade +order), so any consumer-level rule on `body` / `html` wins automatically — no specificity hacks or `!important` needed. Two things to be aware of: - **`overflow: hidden` on ``** — the editor expects a full-viewport canvas with no @@ -245,8 +250,9 @@ specificity hacks or `!important` needed. Two things to be aware of: ``, the token is undefined and the body falls back to transparent. Either ship the CSS or set `--wb-background-color` yourself. -If you don't want the body resets at all, import the SDK before your own global -stylesheet so your rules land in the unlayered cascade above `@layer reset`. +If you don't want the body resets at all, just override them in your own global +stylesheet — unlayered consumer CSS beats `@layer ui.base` regardless of import +order. ## Single-instance constraint diff --git a/packages/sdk/package.json b/packages/sdk/package.json index c2ac9e18c..1234c2c6e 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -70,12 +70,12 @@ "zustand": "^5.0.0" }, "dependencies": { + "@base-ui/react": "catalog:", "@cfworker/json-schema": "4.1.1", "@fontsource/poppins": "^5.2.7", "@jsonforms/core": "^3.4.0", "@jsonforms/react": "^3.4.0", "@phosphor-icons/react": "^2.1.7", - "@synergycodes/overflow-ui": "1.0.0-beta.27", "ace-builds": "^1.43.4", "ajv": "catalog:", "clsx": "^2.1.1", @@ -95,6 +95,7 @@ "@vitejs/plugin-react": "^4.3.4", "@workflow-builder/icons": "workspace:*", "@workflow-builder/types": "workspace:*", + "@workflowbuilder/ui": "workspace:*", "eslint-plugin-react": "7.37.2", "eslint-plugin-react-hooks": "5.1.0-rc.1", "eslint-plugin-tsdoc": "^0.4.0", diff --git a/packages/sdk/src/components/button-submit/button-submit.tsx b/packages/sdk/src/components/button-submit/button-submit.tsx index c540d3f12..57810ed7d 100644 --- a/packages/sdk/src/components/button-submit/button-submit.tsx +++ b/packages/sdk/src/components/button-submit/button-submit.tsx @@ -1,5 +1,5 @@ import { Spinner } from '@phosphor-icons/react'; -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import clsx from 'clsx'; import styles from './button-submit.module.css'; diff --git a/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.module.css b/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.module.css index 17abb2db4..24b742f09 100644 --- a/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.module.css +++ b/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.module.css @@ -1,3 +1,5 @@ +@layer ui.base, ui.component; + @layer ui.component { .container { display: flex; diff --git a/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.tsx b/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.tsx index 2c854c88a..95d3d14e5 100644 --- a/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.tsx +++ b/packages/sdk/src/components/form/form-control-with-label/form-control-with-label.tsx @@ -1,4 +1,4 @@ -import type { ItemSize } from '@synergycodes/overflow-ui'; +import type { ItemSize } from '@workflowbuilder/ui'; import clsx from 'clsx'; import type { PropsWithChildren } from 'react'; diff --git a/packages/sdk/src/components/form/label/label.module.css b/packages/sdk/src/components/form/label/label.module.css index cdd818c0b..8a2abf294 100644 --- a/packages/sdk/src/components/form/label/label.module.css +++ b/packages/sdk/src/components/form/label/label.module.css @@ -1,3 +1,5 @@ +@layer ui.base, ui.component; + :root { --ax-public-form-label-color: var(--ax-txt-secondary-default); --ax-public-form-label-asterisk-color: var(--ax-txt-error-default); diff --git a/packages/sdk/src/components/form/label/label.tsx b/packages/sdk/src/components/form/label/label.tsx index 50607f247..ada41d85b 100644 --- a/packages/sdk/src/components/form/label/label.tsx +++ b/packages/sdk/src/components/form/label/label.tsx @@ -1,5 +1,5 @@ import { Asterisk } from '@phosphor-icons/react'; -import type { ItemSize } from '@synergycodes/overflow-ui'; +import type { ItemSize } from '@workflowbuilder/ui'; import clsx from 'clsx'; import styles from './label.module.css'; diff --git a/packages/sdk/src/components/sidebar/sidebar.module.css b/packages/sdk/src/components/sidebar/sidebar.module.css index 81c87626b..5f1add45b 100644 --- a/packages/sdk/src/components/sidebar/sidebar.module.css +++ b/packages/sdk/src/components/sidebar/sidebar.module.css @@ -1,3 +1,5 @@ +@layer ui.base, ui.component; + @layer ui.component { .sidebar { height: min-content; diff --git a/packages/sdk/src/components/sidebar/sidebar.tsx b/packages/sdk/src/components/sidebar/sidebar.tsx index a2a9e6fa8..b98bf58be 100644 --- a/packages/sdk/src/components/sidebar/sidebar.tsx +++ b/packages/sdk/src/components/sidebar/sidebar.tsx @@ -1,4 +1,4 @@ -import { Separator } from '@synergycodes/overflow-ui'; +import { Separator } from '@workflowbuilder/ui'; import clsx from 'clsx'; import styles from './sidebar.module.css'; diff --git a/packages/sdk/src/features/app-bar/components/controls/controls.tsx b/packages/sdk/src/features/app-bar/components/controls/controls.tsx index 057410b3f..bbcf74b27 100644 --- a/packages/sdk/src/features/app-bar/components/controls/controls.tsx +++ b/packages/sdk/src/features/app-bar/components/controls/controls.tsx @@ -1,5 +1,5 @@ import { DotsThreeVertical } from '@phosphor-icons/react'; -import { Menu, type MenuItemProps, NavButton } from '@synergycodes/overflow-ui'; +import { Menu, type MenuItemProps, NavButton } from '@workflowbuilder/ui'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/app-bar/components/project-selection/project-selection.spec.tsx b/packages/sdk/src/features/app-bar/components/project-selection/project-selection.spec.tsx index a75151c66..ffe55ca71 100644 --- a/packages/sdk/src/features/app-bar/components/project-selection/project-selection.spec.tsx +++ b/packages/sdk/src/features/app-bar/components/project-selection/project-selection.spec.tsx @@ -1,11 +1,11 @@ -import type { MenuItemProps } from '@synergycodes/overflow-ui'; import { fireEvent, render, screen } from '@testing-library/react'; +import type { MenuItemProps } from '@workflowbuilder/ui'; import { describe, expect, it, vi } from 'vitest'; // Render the menu's `items` inline so we can assert on them without driving the -// real overflow-ui popover. The component also imports Input/NavButton, so the +// real menu popover. The component also imports Input/NavButton, so the // mock must expose them too. -vi.mock('@synergycodes/overflow-ui', () => ({ +vi.mock('@workflowbuilder/ui', () => ({ Menu: ({ items }: { items: MenuItemProps[] }) => (
    {items.map((item) => ( diff --git a/packages/sdk/src/features/app-bar/components/project-selection/project-selection.tsx b/packages/sdk/src/features/app-bar/components/project-selection/project-selection.tsx index 6ca66b438..e25a62748 100644 --- a/packages/sdk/src/features/app-bar/components/project-selection/project-selection.tsx +++ b/packages/sdk/src/features/app-bar/components/project-selection/project-selection.tsx @@ -1,5 +1,5 @@ import { CaretDown } from '@phosphor-icons/react'; -import { Input, Menu, NavButton } from '@synergycodes/overflow-ui'; +import { Input, Menu, NavButton } from '@workflowbuilder/ui'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/app-bar/components/toggle-dark-mode/toggle-dark-mode.tsx b/packages/sdk/src/features/app-bar/components/toggle-dark-mode/toggle-dark-mode.tsx index dcf570895..8c55abe32 100644 --- a/packages/sdk/src/features/app-bar/components/toggle-dark-mode/toggle-dark-mode.tsx +++ b/packages/sdk/src/features/app-bar/components/toggle-dark-mode/toggle-dark-mode.tsx @@ -1,5 +1,5 @@ import { MoonStars, Sun } from '@phosphor-icons/react'; -import { IconSwitch } from '@synergycodes/overflow-ui'; +import { IconSwitch } from '@workflowbuilder/ui'; import { useTheme } from '../../../../hooks/use-theme'; diff --git a/packages/sdk/src/features/app-bar/components/toggle-read-only-mode/toggle-read-only-mode.tsx b/packages/sdk/src/features/app-bar/components/toggle-read-only-mode/toggle-read-only-mode.tsx index 1bf769317..7b025b495 100644 --- a/packages/sdk/src/features/app-bar/components/toggle-read-only-mode/toggle-read-only-mode.tsx +++ b/packages/sdk/src/features/app-bar/components/toggle-read-only-mode/toggle-read-only-mode.tsx @@ -1,5 +1,5 @@ import { PencilSimple, PencilSimpleSlash } from '@phosphor-icons/react'; -import { IconSwitch } from '@synergycodes/overflow-ui'; +import { IconSwitch } from '@workflowbuilder/ui'; import { useStore } from '../../../../store/store'; diff --git a/packages/sdk/src/features/app-bar/functions/get-controls-dots-items.tsx b/packages/sdk/src/features/app-bar/functions/get-controls-dots-items.tsx index e1a1af044..924617c32 100644 --- a/packages/sdk/src/features/app-bar/functions/get-controls-dots-items.tsx +++ b/packages/sdk/src/features/app-bar/functions/get-controls-dots-items.tsx @@ -1,4 +1,4 @@ -import type { MenuItemProps } from '@synergycodes/overflow-ui'; +import type { MenuItemProps } from '@workflowbuilder/ui'; import i18n from 'i18next'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/diagram/diagram.tsx b/packages/sdk/src/features/diagram/diagram.tsx index 8e38e557c..466cd50bd 100644 --- a/packages/sdk/src/features/diagram/diagram.tsx +++ b/packages/sdk/src/features/diagram/diagram.tsx @@ -13,7 +13,6 @@ import { type DragEventHandler, useCallback, useEffect, useMemo } from 'react'; import type { DragEvent } from 'react'; import styles from './diagram.module.css'; -import '@xyflow/react/dist/style.css'; import { getReactFlowProps } from '../../data/react-flow-config'; import { usePaletteDrop } from '../../hooks/use-palette-drop'; diff --git a/packages/sdk/src/features/diagram/edges/edge-label-renderer/edge-label-renderer.tsx b/packages/sdk/src/features/diagram/edges/edge-label-renderer/edge-label-renderer.tsx index 02ff2e90f..525a093a4 100644 --- a/packages/sdk/src/features/diagram/edges/edge-label-renderer/edge-label-renderer.tsx +++ b/packages/sdk/src/features/diagram/edges/edge-label-renderer/edge-label-renderer.tsx @@ -1,4 +1,4 @@ -import { EdgeLabel as Label } from '@synergycodes/overflow-ui'; +import { EdgeLabel as Label } from '@workflowbuilder/ui'; import { EdgeLabelRenderer } from '@xyflow/react'; import type { CSSProperties } from 'react'; @@ -18,7 +18,7 @@ type EdgeLabelProps = { /** * Renders a label (text or icon) at fixed canvas coordinates, used by edge * components to attach descriptive content along their path. Built on top - * of xyflow's `` and styled via overflow-ui's + * of xyflow's `` and styled via `@workflowbuilder/ui`'s * `` primitive so hover / selected states match the rest of * the editor. * diff --git a/packages/sdk/src/features/diagram/edges/label-edge/use-label-edge-hover.ts b/packages/sdk/src/features/diagram/edges/label-edge/use-label-edge-hover.ts index 2283db712..c23cd5295 100644 --- a/packages/sdk/src/features/diagram/edges/label-edge/use-label-edge-hover.ts +++ b/packages/sdk/src/features/diagram/edges/label-edge/use-label-edge-hover.ts @@ -1,4 +1,4 @@ -import { type EdgeState, useEdgeStyle } from '@synergycodes/overflow-ui'; +import { type EdgeState, useEdgeStyle } from '@workflowbuilder/ui'; import { useState } from 'react'; import { useStore } from '../../../../store/store'; diff --git a/packages/sdk/src/features/diagram/edges/self-connecting-edge/self-connecting-edge.tsx b/packages/sdk/src/features/diagram/edges/self-connecting-edge/self-connecting-edge.tsx index cebd27e72..6181f2838 100644 --- a/packages/sdk/src/features/diagram/edges/self-connecting-edge/self-connecting-edge.tsx +++ b/packages/sdk/src/features/diagram/edges/self-connecting-edge/self-connecting-edge.tsx @@ -1,4 +1,4 @@ -import { type EdgeState, useEdgeStyle } from '@synergycodes/overflow-ui'; +import { type EdgeState, useEdgeStyle } from '@workflowbuilder/ui'; import type { EdgeProps } from '@xyflow/react'; import type { WorkflowBuilderEdge } from '../../../../node/node-data'; diff --git a/packages/sdk/src/features/diagram/edges/temporary-edge/temporary-edge.tsx b/packages/sdk/src/features/diagram/edges/temporary-edge/temporary-edge.tsx index 1a8e0d67c..000be04e2 100644 --- a/packages/sdk/src/features/diagram/edges/temporary-edge/temporary-edge.tsx +++ b/packages/sdk/src/features/diagram/edges/temporary-edge/temporary-edge.tsx @@ -1,4 +1,4 @@ -import { useEdgeStyle } from '@synergycodes/overflow-ui'; +import { useEdgeStyle } from '@workflowbuilder/ui'; import { type ConnectionLineComponentProps, getSmoothStepPath } from '@xyflow/react'; import { EDGE_CURVE_RADIUS, EDGE_OFFSET } from '../edge.consts'; diff --git a/packages/sdk/src/features/diagram/handles/get-handles-alignment.ts b/packages/sdk/src/features/diagram/handles/get-handles-alignment.ts index b64b83367..8fb87c6f6 100644 --- a/packages/sdk/src/features/diagram/handles/get-handles-alignment.ts +++ b/packages/sdk/src/features/diagram/handles/get-handles-alignment.ts @@ -1,11 +1,11 @@ -import type { NodePanel } from '@synergycodes/overflow-ui'; +import type { NodePanel } from '@workflowbuilder/ui'; import type { ComponentProps } from 'react'; import type { LayoutDirection } from '../../../node/common'; -// Source-of-truth: overflow-ui's `` prop, so adding a new -// alignment in overflow-ui surfaces here as a type error instead of silently -// drifting. +// Source-of-truth: `@workflowbuilder/ui`'s `` prop, so adding +// a new alignment in the UI library surfaces here as a type error instead of +// silently drifting. type HandlesAlignment = NonNullable['alignment']>; // Unifies how built-in node templates choose the `alignment` prop they pass to diff --git a/packages/sdk/src/features/diagram/hooks/use-on-connect.spec.tsx b/packages/sdk/src/features/diagram/hooks/use-on-connect.spec.tsx index e98e568f1..995c6035b 100644 --- a/packages/sdk/src/features/diagram/hooks/use-on-connect.spec.tsx +++ b/packages/sdk/src/features/diagram/hooks/use-on-connect.spec.tsx @@ -2,7 +2,7 @@ // the connection handlers must be inert and warn the user; in editable mode they // must forward to the store and track the change. // -// `@synergycodes/overflow-ui` is mocked because the hook imports `SnackbarType` +// `@workflowbuilder/ui` is mocked because the hook imports `SnackbarType` // from it, and the package's JS carries a CSS side-effect import vitest's // node-side transformer can't process. `show-snackbar` is mocked so the user // feedback can be asserted without rendering the snackbar stack. @@ -13,7 +13,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { resetWorkflowStore, useStore } from '../../../store/store'; import { useConnect } from './use-on-connect'; -vi.mock('@synergycodes/overflow-ui', () => ({ +vi.mock('@workflowbuilder/ui', () => ({ SnackbarType: { SUCCESS: 'success', WARNING: 'warning', ERROR: 'error', INFO: 'info' }, })); diff --git a/packages/sdk/src/features/diagram/hooks/use-on-connect.tsx b/packages/sdk/src/features/diagram/hooks/use-on-connect.tsx index fdea2cce8..29aebb021 100644 --- a/packages/sdk/src/features/diagram/hooks/use-on-connect.tsx +++ b/packages/sdk/src/features/diagram/hooks/use-on-connect.tsx @@ -1,4 +1,4 @@ -import { SnackbarType } from '@synergycodes/overflow-ui'; +import { SnackbarType } from '@workflowbuilder/ui'; import type { OnConnect, OnConnectEnd, OnConnectStart } from '@xyflow/react'; import { useCallback } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/ai-agent-node-template/ai-agent-node-template.tsx b/packages/sdk/src/features/diagram/nodes/ai-agent-node-template/ai-agent-node-template.tsx index b20dd2e11..ef7b1ce9c 100644 --- a/packages/sdk/src/features/diagram/nodes/ai-agent-node-template/ai-agent-node-template.tsx +++ b/packages/sdk/src/features/diagram/nodes/ai-agent-node-template/ai-agent-node-template.tsx @@ -1,4 +1,4 @@ -import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@synergycodes/overflow-ui'; +import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@workflowbuilder/ui'; import { Handle } from '@xyflow/react'; import clsx from 'clsx'; import { memo, useMemo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/ai-node-container.tsx b/packages/sdk/src/features/diagram/nodes/ai-node-container.tsx index 14cdf566a..ad7ec3420 100644 --- a/packages/sdk/src/features/diagram/nodes/ai-node-container.tsx +++ b/packages/sdk/src/features/diagram/nodes/ai-node-container.tsx @@ -1,4 +1,4 @@ -import { NodeAsPortWrapper } from '@synergycodes/overflow-ui'; +import { NodeAsPortWrapper } from '@workflowbuilder/ui'; import { type Node, type NodeProps, Position } from '@xyflow/react'; import { memo, useMemo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx index e356ab649..02463a587 100644 --- a/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx +++ b/packages/sdk/src/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx @@ -1,5 +1,5 @@ import { PlusCircle } from '@phosphor-icons/react'; -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import styles from './placeholder-button.module.css'; diff --git a/packages/sdk/src/features/diagram/nodes/decision-node-container.tsx b/packages/sdk/src/features/diagram/nodes/decision-node-container.tsx index 20208818c..ac91561c3 100644 --- a/packages/sdk/src/features/diagram/nodes/decision-node-container.tsx +++ b/packages/sdk/src/features/diagram/nodes/decision-node-container.tsx @@ -1,4 +1,4 @@ -import { NodeAsPortWrapper } from '@synergycodes/overflow-ui'; +import { NodeAsPortWrapper } from '@workflowbuilder/ui'; import type { Node, NodeProps } from '@xyflow/react'; import { memo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/decision-node-template/decision-node-template.tsx b/packages/sdk/src/features/diagram/nodes/decision-node-template/decision-node-template.tsx index b3d8638e7..3f7c2766c 100644 --- a/packages/sdk/src/features/diagram/nodes/decision-node-template/decision-node-template.tsx +++ b/packages/sdk/src/features/diagram/nodes/decision-node-template/decision-node-template.tsx @@ -1,4 +1,4 @@ -import { NodeDescription, NodeIcon, NodePanel, Status } from '@synergycodes/overflow-ui'; +import { NodeDescription, NodeIcon, NodePanel, Status } from '@workflowbuilder/ui'; import { Handle } from '@xyflow/react'; import { memo, useMemo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/node-container.tsx b/packages/sdk/src/features/diagram/nodes/node-container.tsx index 9df6c652d..d0566f438 100644 --- a/packages/sdk/src/features/diagram/nodes/node-container.tsx +++ b/packages/sdk/src/features/diagram/nodes/node-container.tsx @@ -1,4 +1,4 @@ -import { NodeAsPortWrapper } from '@synergycodes/overflow-ui'; +import { NodeAsPortWrapper } from '@workflowbuilder/ui'; import type { NodeProps } from '@xyflow/react'; import { memo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/start-node-container.tsx b/packages/sdk/src/features/diagram/nodes/start-node-container.tsx index 33348fe35..890fe5bb2 100644 --- a/packages/sdk/src/features/diagram/nodes/start-node-container.tsx +++ b/packages/sdk/src/features/diagram/nodes/start-node-container.tsx @@ -1,4 +1,4 @@ -import { NodeAsPortWrapper } from '@synergycodes/overflow-ui'; +import { NodeAsPortWrapper } from '@workflowbuilder/ui'; import type { NodeProps } from '@xyflow/react'; import { memo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/start-node-template/start-node-template.tsx b/packages/sdk/src/features/diagram/nodes/start-node-template/start-node-template.tsx index 21f631c26..6642d78b5 100644 --- a/packages/sdk/src/features/diagram/nodes/start-node-template/start-node-template.tsx +++ b/packages/sdk/src/features/diagram/nodes/start-node-template/start-node-template.tsx @@ -1,4 +1,4 @@ -import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@synergycodes/overflow-ui'; +import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@workflowbuilder/ui'; import { Handle } from '@xyflow/react'; import { memo, useMemo } from 'react'; diff --git a/packages/sdk/src/features/diagram/nodes/workflow-node-template/workflow-node-template.tsx b/packages/sdk/src/features/diagram/nodes/workflow-node-template/workflow-node-template.tsx index c7cbcae93..3f1a2005c 100644 --- a/packages/sdk/src/features/diagram/nodes/workflow-node-template/workflow-node-template.tsx +++ b/packages/sdk/src/features/diagram/nodes/workflow-node-template/workflow-node-template.tsx @@ -1,4 +1,4 @@ -import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@synergycodes/overflow-ui'; +import { Collapsible, NodeDescription, NodeIcon, NodePanel, Status } from '@workflowbuilder/ui'; import { Handle } from '@xyflow/react'; import { memo, useMemo } from 'react'; diff --git a/packages/sdk/src/features/i18n/components/language-selector/language-selector.spec.tsx b/packages/sdk/src/features/i18n/components/language-selector/language-selector.spec.tsx index 4064745f1..3ad24e990 100644 --- a/packages/sdk/src/features/i18n/components/language-selector/language-selector.spec.tsx +++ b/packages/sdk/src/features/i18n/components/language-selector/language-selector.spec.tsx @@ -6,7 +6,7 @@ import { describe, expect, it, vi } from 'vitest'; const i18nState = { language: 'en', resolvedLanguage: 'en', changeLanguage: vi.fn() }; // Render the Menu's trigger (children) so the displayed language code is queryable. -vi.mock('@synergycodes/overflow-ui', () => ({ +vi.mock('@workflowbuilder/ui', () => ({ Menu: ({ children }: { children?: ReactNode }) =>
    {children}
    , NavButton: ({ children }: { children?: ReactNode }) => , })); diff --git a/packages/sdk/src/features/i18n/components/language-selector/language-selector.tsx b/packages/sdk/src/features/i18n/components/language-selector/language-selector.tsx index 498b1279f..7d0b53d4e 100644 --- a/packages/sdk/src/features/i18n/components/language-selector/language-selector.tsx +++ b/packages/sdk/src/features/i18n/components/language-selector/language-selector.tsx @@ -1,5 +1,5 @@ import { CaretDown } from '@phosphor-icons/react'; -import { Menu, type MenuItemProps, NavButton } from '@synergycodes/overflow-ui'; +import { Menu, type MenuItemProps, NavButton } from '@workflowbuilder/ui'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx b/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx index 4dfc6904d..db77b0809 100644 --- a/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx +++ b/packages/sdk/src/features/integration/components/import-export/export-modal/export-modal.tsx @@ -1,4 +1,4 @@ -import { Button, SnackbarType } from '@synergycodes/overflow-ui'; +import { Button, SnackbarType } from '@workflowbuilder/ui'; import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx b/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx index 06532b073..6f2710374 100644 --- a/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx +++ b/packages/sdk/src/features/integration/components/import-export/import-modal/import-modal.tsx @@ -1,4 +1,4 @@ -import { Button, SnackbarType } from '@synergycodes/overflow-ui'; +import { Button, SnackbarType } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/integration/components/save-button/save-button.tsx b/packages/sdk/src/features/integration/components/save-button/save-button.tsx index bcfe4750e..01a9d3954 100644 --- a/packages/sdk/src/features/integration/components/save-button/save-button.tsx +++ b/packages/sdk/src/features/integration/components/save-button/save-button.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import { useContext } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/integration/stores/use-integration-store.spec.ts b/packages/sdk/src/features/integration/stores/use-integration-store.spec.ts index 32570d5c1..b5c91b667 100644 --- a/packages/sdk/src/features/integration/stores/use-integration-store.spec.ts +++ b/packages/sdk/src/features/integration/stores/use-integration-store.spec.ts @@ -5,7 +5,7 @@ import { showSnackbar } from '../../../utils/show-snackbar'; import { openTemplateSelectorModal } from '../../modals/template-selector/open-template-selector-modal'; import { getStoreSavingStatus, loadData, setStoreSavingStatus, useIntegrationStore } from './use-integration-store'; -vi.mock('@synergycodes/overflow-ui', () => ({ +vi.mock('@workflowbuilder/ui', () => ({ SnackbarType: { SUCCESS: 'SUCCESS', ERROR: 'ERROR', INFO: 'INFO', WARNING: 'WARNING' }, })); diff --git a/packages/sdk/src/features/integration/stores/use-integration-store.ts b/packages/sdk/src/features/integration/stores/use-integration-store.ts index 0f26c1ba0..760b79810 100644 --- a/packages/sdk/src/features/integration/stores/use-integration-store.ts +++ b/packages/sdk/src/features/integration/stores/use-integration-store.ts @@ -1,4 +1,4 @@ -import { SnackbarType } from '@synergycodes/overflow-ui'; +import { SnackbarType } from '@workflowbuilder/ui'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; diff --git a/packages/sdk/src/features/integration/utils/show-snackbar.ts b/packages/sdk/src/features/integration/utils/show-snackbar.ts index 061637b6f..6f0dc7d43 100644 --- a/packages/sdk/src/features/integration/utils/show-snackbar.ts +++ b/packages/sdk/src/features/integration/utils/show-snackbar.ts @@ -1,4 +1,4 @@ -import { SnackbarType } from '@synergycodes/overflow-ui'; +import { SnackbarType } from '@workflowbuilder/ui'; import type { OnSaveParams } from '../../../types/integration'; import { showSnackbar } from '../../../utils/show-snackbar'; diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx index a76fb8cb8..cc11d8182 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/ai-tools-control.tsx @@ -1,5 +1,5 @@ import { PlusCircle, Trash } from '@phosphor-icons/react'; -import { Button, NavButton } from '@synergycodes/overflow-ui'; +import { Button, NavButton } from '@workflowbuilder/ui'; import { type ComponentProps, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx index e6ef31297..a226f6b66 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx @@ -1,4 +1,4 @@ -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import styles from './add-ai-tool-footer.module.css'; diff --git a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-form-content/add-ai-tool-form-content.tsx b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-form-content/add-ai-tool-form-content.tsx index cc891de21..bea39d87b 100644 --- a/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-form-content/add-ai-tool-form-content.tsx +++ b/packages/sdk/src/features/json-form/controls/ai-tools-control/components/add-ai-tool-form-content/add-ai-tool-form-content.tsx @@ -1,4 +1,4 @@ -import { Input, Select, TextArea } from '@synergycodes/overflow-ui'; +import { Input, Select, TextArea } from '@workflowbuilder/ui'; import { useCallback, useState } from 'react'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/json-form/controls/date-picker-control/date-picker-control.tsx b/packages/sdk/src/features/json-form/controls/date-picker-control/date-picker-control.tsx index 7dc1e828e..5ec0260da 100644 --- a/packages/sdk/src/features/json-form/controls/date-picker-control/date-picker-control.tsx +++ b/packages/sdk/src/features/json-form/controls/date-picker-control/date-picker-control.tsx @@ -1,4 +1,4 @@ -import { DatePicker, type DatePickerProps } from '@synergycodes/overflow-ui'; +import { DatePicker, type DatePickerProps } from '@workflowbuilder/ui'; import type { DatePickerControlProps } from '../../types/controls'; import { createControlRenderer } from '../../utils/rendering'; diff --git a/packages/sdk/src/features/json-form/controls/decision-branches-control/branch-card/branch-card.tsx b/packages/sdk/src/features/json-form/controls/decision-branches-control/branch-card/branch-card.tsx index 8bc370271..6cc20dd6f 100644 --- a/packages/sdk/src/features/json-form/controls/decision-branches-control/branch-card/branch-card.tsx +++ b/packages/sdk/src/features/json-form/controls/decision-branches-control/branch-card/branch-card.tsx @@ -1,5 +1,5 @@ import { SlidersHorizontal, Trash } from '@phosphor-icons/react'; -import { Input, NavButton } from '@synergycodes/overflow-ui'; +import { Input, NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-condition-modal-footer/condition-modal-footer.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-condition-modal-footer/condition-modal-footer.tsx index 8dce54f70..8d867449f 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-condition-modal-footer/condition-modal-footer.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-condition-modal-footer/condition-modal-footer.tsx @@ -1,4 +1,4 @@ -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; type Props = { diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx index 7d86433e5..ef6327bab 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-control.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form-field/conditions-form-field.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form-field/conditions-form-field.tsx index 9b417761a..b79c8153e 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form-field/conditions-form-field.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form-field/conditions-form-field.tsx @@ -1,4 +1,4 @@ -import { NavButton, SegmentPicker, Select } from '@synergycodes/overflow-ui'; +import { NavButton, SegmentPicker, Select } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx index 71c3dc76d..ccce534ff 100644 --- a/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx +++ b/packages/sdk/src/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.tsx @@ -1,4 +1,4 @@ -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import { type ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/json-form/controls/select-control/select-control.tsx b/packages/sdk/src/features/json-form/controls/select-control/select-control.tsx index b3ca12ed3..cdf1df0b0 100644 --- a/packages/sdk/src/features/json-form/controls/select-control/select-control.tsx +++ b/packages/sdk/src/features/json-form/controls/select-control/select-control.tsx @@ -1,4 +1,4 @@ -import { Select, type SelectBaseProps } from '@synergycodes/overflow-ui'; +import { Select, type SelectBaseProps } from '@workflowbuilder/ui'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/json-form/controls/switch-control/switch-control.tsx b/packages/sdk/src/features/json-form/controls/switch-control/switch-control.tsx index cc31f7c36..fd24f528b 100644 --- a/packages/sdk/src/features/json-form/controls/switch-control/switch-control.tsx +++ b/packages/sdk/src/features/json-form/controls/switch-control/switch-control.tsx @@ -1,4 +1,4 @@ -import { Switch } from '@synergycodes/overflow-ui'; +import { Switch } from '@workflowbuilder/ui'; import type { SwitchControlProps } from '../../types/controls'; import { createControlRenderer } from '../../utils/rendering'; @@ -8,7 +8,7 @@ function SwitchControl(props: SwitchControlProps) { const { data, handleChange, path, enabled, uischema } = props; const isDisabled = !enabled || uischema.disabled === true; - function onChange(checked: boolean, _event: React.ChangeEvent) { + function onChange(checked: boolean) { handleChange(path, checked); } diff --git a/packages/sdk/src/features/json-form/controls/text-area-control/text-area-control.tsx b/packages/sdk/src/features/json-form/controls/text-area-control/text-area-control.tsx index 3af41883f..4b214092f 100644 --- a/packages/sdk/src/features/json-form/controls/text-area-control/text-area-control.tsx +++ b/packages/sdk/src/features/json-form/controls/text-area-control/text-area-control.tsx @@ -1,4 +1,4 @@ -import { TextArea } from '@synergycodes/overflow-ui'; +import { TextArea } from '@workflowbuilder/ui'; import { useEffect, useState } from 'react'; import type { TextAreaControlProps } from '../../types/controls'; diff --git a/packages/sdk/src/features/json-form/controls/text-control/text-control.tsx b/packages/sdk/src/features/json-form/controls/text-control/text-control.tsx index 15fe5a00f..5c2c4e9e4 100644 --- a/packages/sdk/src/features/json-form/controls/text-control/text-control.tsx +++ b/packages/sdk/src/features/json-form/controls/text-control/text-control.tsx @@ -1,4 +1,4 @@ -import { Input } from '@synergycodes/overflow-ui'; +import { Input } from '@workflowbuilder/ui'; import { useEffect, useState } from 'react'; import type { TextControlProps } from '../../types/controls'; diff --git a/packages/sdk/src/features/json-form/layouts/accordion-layout/accordion-layout.tsx b/packages/sdk/src/features/json-form/layouts/accordion-layout/accordion-layout.tsx index b7a16e955..5e0df9284 100644 --- a/packages/sdk/src/features/json-form/layouts/accordion-layout/accordion-layout.tsx +++ b/packages/sdk/src/features/json-form/layouts/accordion-layout/accordion-layout.tsx @@ -1,4 +1,4 @@ -import { Accordion } from '@synergycodes/overflow-ui'; +import { Accordion } from '@workflowbuilder/ui'; import styles from './accordion-layout.module.css'; diff --git a/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx b/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx index d68156672..3c07b9fcd 100644 --- a/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx +++ b/packages/sdk/src/features/modals/delete-confirmation/delete-confirmation.tsx @@ -1,4 +1,4 @@ -import { Button, Checkbox } from '@synergycodes/overflow-ui'; +import { Button, Checkbox } from '@workflowbuilder/ui'; import type { Edge, Node } from '@xyflow/react'; import { useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/modals/providers/modal-provider.tsx b/packages/sdk/src/features/modals/providers/modal-provider.tsx index dbad3438c..8752304d0 100644 --- a/packages/sdk/src/features/modals/providers/modal-provider.tsx +++ b/packages/sdk/src/features/modals/providers/modal-provider.tsx @@ -1,30 +1,57 @@ -import { Modal } from '@synergycodes/overflow-ui'; +import { Modal } from '@workflowbuilder/ui'; +import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { closeModal, useModalStore } from '../stores/use-modal-store'; +// Read by variable-text.module.css to suppress its own backdrop. +const MODAL_OPEN_BODY_CLASS = 'wb-modal-open'; + export function ModalProvider() { const isOpen = useModalStore((state) => state.isOpen); const modal = useModalStore((state) => state.modal); - if (!isOpen || !modal) { - return null; - } + // The store clears `modal` on close - keep it for the exit transition. + const lastModalRef = useRef(modal); + + useEffect(() => { + if (modal) { + lastModalRef.current = modal; + } + }, [modal]); + + useEffect(() => { + document.body.classList.toggle(MODAL_OPEN_BODY_CLASS, isOpen); + return () => { + document.body.classList.remove(MODAL_OPEN_BODY_CLASS); + }; + }, [isOpen]); + + // The portal touches `document` on every render - keep it out of SSR. + const [mounted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + + const activeModal = modal ?? lastModalRef.current; + + if (!mounted) return null; + // Transitions only run when `open` toggles on an already-mounted root. return ( <> {createPortal( - {modal.content} + {activeModal?.content} , document.body, )} diff --git a/packages/sdk/src/features/modals/stores/use-modal-store.ts b/packages/sdk/src/features/modals/stores/use-modal-store.ts index 40fd566b6..91000304a 100644 --- a/packages/sdk/src/features/modals/stores/use-modal-store.ts +++ b/packages/sdk/src/features/modals/stores/use-modal-store.ts @@ -1,4 +1,4 @@ -import type { FooterVariant, Modal } from '@synergycodes/overflow-ui'; +import type { FooterVariant, Modal } from '@workflowbuilder/ui'; import type { ComponentProps } from 'react'; import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; diff --git a/packages/sdk/src/features/palette/components/footer/palette-footer.tsx b/packages/sdk/src/features/palette/components/footer/palette-footer.tsx index f34604328..872236283 100644 --- a/packages/sdk/src/features/palette/components/footer/palette-footer.tsx +++ b/packages/sdk/src/features/palette/components/footer/palette-footer.tsx @@ -1,4 +1,4 @@ -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; import styles from './palette-footer.module.css'; diff --git a/packages/sdk/src/features/palette/components/header/palette-header.tsx b/packages/sdk/src/features/palette/components/header/palette-header.tsx index 6f9d9299f..a4ad59477 100644 --- a/packages/sdk/src/features/palette/components/header/palette-header.tsx +++ b/packages/sdk/src/features/palette/components/header/palette-header.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/palette/components/items/palette-items.tsx b/packages/sdk/src/features/palette/components/items/palette-items.tsx index ad9cb32f9..2591eaabf 100644 --- a/packages/sdk/src/features/palette/components/items/palette-items.tsx +++ b/packages/sdk/src/features/palette/components/items/palette-items.tsx @@ -1,4 +1,4 @@ -import { Accordion } from '@synergycodes/overflow-ui'; +import { Accordion } from '@workflowbuilder/ui'; import type { DragEvent } from 'react'; import styles from './palette-items.module.css'; diff --git a/packages/sdk/src/features/properties-bar/components/edge-properties/edge-properties.tsx b/packages/sdk/src/features/properties-bar/components/edge-properties/edge-properties.tsx index e3edad031..ed6f59404 100644 --- a/packages/sdk/src/features/properties-bar/components/edge-properties/edge-properties.tsx +++ b/packages/sdk/src/features/properties-bar/components/edge-properties/edge-properties.tsx @@ -1,4 +1,4 @@ -import { Input } from '@synergycodes/overflow-ui'; +import { Input } from '@workflowbuilder/ui'; import { useEffect, useState } from 'react'; import styles from './edge-properties.module.css'; diff --git a/packages/sdk/src/features/properties-bar/components/header/properties-bar-header.tsx b/packages/sdk/src/features/properties-bar/components/header/properties-bar-header.tsx index dd1f4df6f..059c7a0e9 100644 --- a/packages/sdk/src/features/properties-bar/components/header/properties-bar-header.tsx +++ b/packages/sdk/src/features/properties-bar/components/header/properties-bar-header.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import { useTranslation } from 'react-i18next'; import { Icon } from '@workflow-builder/icons'; diff --git a/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx b/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx index 1c23f7141..3099440c0 100644 --- a/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx +++ b/packages/sdk/src/features/properties-bar/components/properties-bar/properties-bar.tsx @@ -1,4 +1,4 @@ -import { Button, SegmentPicker } from '@synergycodes/overflow-ui'; +import { Button, SegmentPicker } from '@workflowbuilder/ui'; import { useState } from 'react'; import styles from './properties-bar.module.css'; diff --git a/packages/sdk/src/features/snackbar/snackbar-container.tsx b/packages/sdk/src/features/snackbar/snackbar-container.tsx index 2d8484b04..a54f4320b 100644 --- a/packages/sdk/src/features/snackbar/snackbar-container.tsx +++ b/packages/sdk/src/features/snackbar/snackbar-container.tsx @@ -1,4 +1,4 @@ -import { Snackbar } from '@synergycodes/overflow-ui'; +import { Snackbar } from '@workflowbuilder/ui'; import { SnackbarProvider } from 'notistack'; export function SnackbarContainer() { diff --git a/packages/sdk/src/features/syntax-highlighter/components/syntax-highlighter-lazy.tsx b/packages/sdk/src/features/syntax-highlighter/components/syntax-highlighter-lazy.tsx index 740a139d0..64e5ae191 100644 --- a/packages/sdk/src/features/syntax-highlighter/components/syntax-highlighter-lazy.tsx +++ b/packages/sdk/src/features/syntax-highlighter/components/syntax-highlighter-lazy.tsx @@ -1,4 +1,4 @@ -import { TextArea } from '@synergycodes/overflow-ui'; +import { TextArea } from '@workflowbuilder/ui'; import React, { Suspense } from 'react'; import type { SyntaxHighlighterProps } from './syntax-highlighter'; diff --git a/packages/sdk/src/features/variables/components/dynamic-typed-input/constants.ts b/packages/sdk/src/features/variables/components/dynamic-typed-input/constants.ts index ab8ec806b..4140f434f 100644 --- a/packages/sdk/src/features/variables/components/dynamic-typed-input/constants.ts +++ b/packages/sdk/src/features/variables/components/dynamic-typed-input/constants.ts @@ -1,4 +1,4 @@ -import type { SelectItem } from '@synergycodes/overflow-ui'; +import type { SelectItem } from '@workflowbuilder/ui'; import type { VariableTypePrimitive } from '../../../../node/node-output-schema'; diff --git a/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.module.css b/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.module.css index 5475d5dc4..d1aadbc3e 100644 --- a/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.module.css +++ b/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.module.css @@ -5,8 +5,8 @@ height: 2.5rem; } -.date-picker, .container--select { + position: relative; border-radius: var(--ax-public-input-border-radius-medium); > div > button { @@ -15,27 +15,14 @@ } } -.date-with-reset-container { - position: relative; -} - -.cursor-pointer { - cursor: pointer; -} - -.reset-button { - position: absolute; - top: var(--ax-token-spacing-input-m-v-pad); - right: var(--ax-token-spacing-input-m-h-pad); - cursor: pointer; +.date-picker { + border-radius: var(--ax-public-input-border-radius-medium); + min-height: 2.5rem; + text-align: left; } -.container--select { +.date-with-reset-container { position: relative; - - > select { - min-height: 41px; - } } .adornment--select { diff --git a/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.tsx b/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.tsx index 0f4ea50d6..36fd873ff 100644 --- a/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.tsx +++ b/packages/sdk/src/features/variables/components/dynamic-typed-input/dynamic-typed-input.tsx @@ -1,4 +1,4 @@ -import { DatePicker, Input, Select } from '@synergycodes/overflow-ui'; +import { DatePicker, Input, Select } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -129,7 +129,7 @@ export function DynamicTypedInput({ onChange(date.toISOString()); }} - valueFormat={'DD-MM-YYYY'} + valueFormat={'dd-MM-yyyy'} placeholder={placeholder || 'DD-MM-YYYY'} error={isError} disabled={disabled} @@ -153,7 +153,7 @@ export function DynamicTypedInput({ onChange(date.toISOString()); setTime(getTimeFromDateIfValid(date.toISOString())); }} - valueFormat="DD-MM-YYYY" + valueFormat="dd-MM-yyyy" placeholder="DD-MM-YYYY" // Uncomment to see times in value // valueFormat="DD-MM-YYYY HH:mm" diff --git a/packages/sdk/src/features/variables/components/dynamic-typed-variable-or-input/dynamic-typed-variable-or-input.tsx b/packages/sdk/src/features/variables/components/dynamic-typed-variable-or-input/dynamic-typed-variable-or-input.tsx index f453b6627..d079c1ccb 100644 --- a/packages/sdk/src/features/variables/components/dynamic-typed-variable-or-input/dynamic-typed-variable-or-input.tsx +++ b/packages/sdk/src/features/variables/components/dynamic-typed-variable-or-input/dynamic-typed-variable-or-input.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/variables/components/variable-text/variable-text.module.css b/packages/sdk/src/features/variables/components/variable-text/variable-text.module.css index 1db39b970..4794b78fc 100644 --- a/packages/sdk/src/features/variables/components/variable-text/variable-text.module.css +++ b/packages/sdk/src/features/variables/components/variable-text/variable-text.module.css @@ -131,7 +131,8 @@ body:has(.suggestionsContainer) { z-index: 200; } - &:has(:global(.base-Modal-root))::before { + /* Modal already dims the page; don't stack a second backdrop. */ + &:global(.wb-modal-open)::before { display: none; } } diff --git a/packages/sdk/src/features/variables/components/variable-text/variable-text.tsx b/packages/sdk/src/features/variables/components/variable-text/variable-text.tsx index 2374ca35a..8acc7b417 100644 --- a/packages/sdk/src/features/variables/components/variable-text/variable-text.tsx +++ b/packages/sdk/src/features/variables/components/variable-text/variable-text.tsx @@ -1,4 +1,4 @@ -import { NavButton, SnackbarType } from '@synergycodes/overflow-ui'; +import { NavButton, SnackbarType } from '@workflowbuilder/ui'; import { type ReactElement, type ReactNode, cloneElement, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Mention, type MentionDataItem, MentionsInput } from 'react-mentions-ts'; diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx index 1327f7c8e..ab691e0c6 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/pane-list/pane-list.tsx @@ -1,4 +1,4 @@ -import { Button } from '@synergycodes/overflow-ui'; +import { Button } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx index 6947adc5a..98a75d12d 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-form/variable-form.tsx @@ -1,4 +1,4 @@ -import { Input, Select, type SelectItem, TextArea } from '@synergycodes/overflow-ui'; +import { Input, Select, type SelectItem, TextArea } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-preview/variable-preview.tsx b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-preview/variable-preview.tsx index 607d1efac..a872a1f5c 100644 --- a/packages/sdk/src/features/variables/modals/tab-global-variables/variable-preview/variable-preview.tsx +++ b/packages/sdk/src/features/variables/modals/tab-global-variables/variable-preview/variable-preview.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/features/variables/modals/tab/tab-header.tsx b/packages/sdk/src/features/variables/modals/tab/tab-header.tsx index 34b9f82ba..2d417d231 100644 --- a/packages/sdk/src/features/variables/modals/tab/tab-header.tsx +++ b/packages/sdk/src/features/variables/modals/tab/tab-header.tsx @@ -1,4 +1,4 @@ -import { NavButton } from '@synergycodes/overflow-ui'; +import { NavButton } from '@workflowbuilder/ui'; import clsx from 'clsx'; import type { PropsWithChildren } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/packages/sdk/src/hooks/use-workflow-builder-actions.spec.tsx b/packages/sdk/src/hooks/use-workflow-builder-actions.spec.tsx index aebe968c4..69e4028a1 100644 --- a/packages/sdk/src/hooks/use-workflow-builder-actions.spec.tsx +++ b/packages/sdk/src/hooks/use-workflow-builder-actions.spec.tsx @@ -23,7 +23,7 @@ vi.mock('../features/variables/modals/modal-settings', () => ({ })); // Short-circuit the use-integration-store chain that drags in -// @synergycodes/overflow-ui (CSS side-effect that vitest's jsdom env can't load). +// @workflowbuilder/ui (CSS side-effect that vitest's jsdom env can't load). vi.mock('@/features/integration/stores/use-integration-store', () => ({ getStoreSavingStatus: vi.fn(), setStoreSavingStatus: vi.fn(), diff --git a/packages/sdk/src/index.css b/packages/sdk/src/index.css index 505172675..c5fec53ae 100644 --- a/packages/sdk/src/index.css +++ b/packages/sdk/src/index.css @@ -1,15 +1,14 @@ -/* Library CSS bundle - ORDER MATTERS — @layer declaration and @imports must come before any rule */ +/* The @layer declaration and every @import must precede any rule. -/* 1. Layer order declaration — empty @layer is allowed before @import */ -@layer reset, ext-lib, ui; + `ui` is the only top-level layer on purpose: layer order is fixed by first + mention anywhere in the document and UI chunk CSS can parse before this + file, so a second top-level layer would let load order invert the cascade. + See packages/ui/css-layers.md. */ +@layer ui.base, ui.component; -/* 2. Web fonts — Poppins is bundled locally (via @fontsource/poppins) instead - of fetched from Google's CDN at runtime. Reasons: strict CSPs block the - external request, the IP leak before consent breaks GDPR-controlled - flows, and air-gapped deployments have no outbound internet at all. - Latin + latin-ext subsets only (covers English + Polish, the SDK's - shipping locales). Override `--wb-font-family` to use a different face. */ +/* Poppins is bundled rather than fetched from a CDN: strict CSPs block the + request, the pre-consent IP leak breaks GDPR flows, and air-gapped + deployments have no outbound internet. */ @import '@fontsource/poppins/latin-300.css'; @import '@fontsource/poppins/latin-400.css'; @import '@fontsource/poppins/latin-500.css'; @@ -21,31 +20,26 @@ @import '@fontsource/poppins/latin-ext-600.css'; @import '@fontsource/poppins/latin-ext-700.css'; -/* 3. Design tokens */ -@import '@synergycodes/overflow-ui/tokens.css'; +@import '@workflowbuilder/ui/tokens.css'; -/* 4. External component CSS */ -@import '@xyflow/react/dist/style.css'; -@import 'overflow-ui-css'; +/* @workflowbuilder/ui styles arrive through the JS module graph - importing + its index.css here would bundle every rule twice. */ +@import '@xyflow/react/dist/style.css' layer(ui.base); -/* 5. Sub-layer order inside `ui` — must come AFTER @imports (has a body, so - the spec classifies it as a statement that closes the @import prelude). */ -@layer ui { - @layer base, component; -} - -:root { - --wb-transition: 0.1s ease-in; - --wb-background-color: var(--ax-ui-bg-tertiary-default); - --wb-scroll-width: 0.375rem; - --wb-scroll-radius: 0.625rem; - --wb-scroll-thumb-color: var(--ax-scrollbar-bg-default); - --wb-scroll-track-color: transparent; - --wb-font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; +/* Layered, so an unlayered consumer override wins whatever the load order. */ +@layer ui.base { + :root { + --wb-transition: 0.1s ease-in; + --wb-background-color: var(--ax-ui-bg-tertiary-default); + --wb-scroll-width: 0.375rem; + --wb-scroll-radius: 0.625rem; + --wb-scroll-thumb-color: var(--ax-scrollbar-bg-default); + --wb-scroll-track-color: transparent; + --wb-font-family: 'Poppins', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + } } -/* 5. App global styles (reset, scrollbars, fonts, body) */ -@layer reset { +@layer ui.base { ::-webkit-scrollbar { width: var(--wb-scroll-width); } @@ -92,10 +86,3 @@ } } } - -/* Shows date picker over modal */ -body:has(.base-Modal-root) { - .mantine-Popover-dropdown { - z-index: 1001 !important; - } -} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index f1a3819c9..e8a970338 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,6 +1,6 @@ // Library entry point for @workflowbuilder/sdk. // Built by packages/sdk/vite.config.mts into dist/ as a distributable npm package. -// Bundles all required CSS (app + xyflow + overflow-ui) into style.css. +// Bundles all required CSS (app + xyflow + @workflowbuilder/ui) into style.css. import './index.css'; import './bootstrap'; diff --git a/packages/sdk/src/types/controls.ts b/packages/sdk/src/types/controls.ts index 6259b277c..ac609a739 100644 --- a/packages/sdk/src/types/controls.ts +++ b/packages/sdk/src/types/controls.ts @@ -1,5 +1,5 @@ import type { ControlElement, ControlProps as JsonFormsControlProps } from '@jsonforms/core'; -import type { InputProps, TextAreaProps } from '@synergycodes/overflow-ui'; +import type { InputProps, TextAreaProps } from '@workflowbuilder/ui'; import type { ComparisonOperator, LogicalOperator } from '../features/variables/constants'; import type { FieldSchema } from '../node/node-schema'; diff --git a/packages/sdk/src/types/labels.ts b/packages/sdk/src/types/labels.ts index c8b3741b6..0ab3d22ae 100644 --- a/packages/sdk/src/types/labels.ts +++ b/packages/sdk/src/types/labels.ts @@ -1,5 +1,5 @@ import type { LabelElement as BaseLabelElement } from '@jsonforms/core'; -import type { ItemSize } from '@synergycodes/overflow-ui'; +import type { ItemSize } from '@workflowbuilder/ui'; import type { LabelVariant } from '../components/form/label/label'; import type { Override } from './utils'; diff --git a/packages/sdk/src/utils/show-snackbar.tsx b/packages/sdk/src/utils/show-snackbar.tsx index b168293e5..479530cc4 100644 --- a/packages/sdk/src/utils/show-snackbar.tsx +++ b/packages/sdk/src/utils/show-snackbar.tsx @@ -1,4 +1,4 @@ -import { Snackbar, type SnackbarProps } from '@synergycodes/overflow-ui'; +import { Snackbar, type SnackbarProps } from '@workflowbuilder/ui'; import i18n from 'i18next'; import { closeSnackbar, enqueueSnackbar } from 'notistack'; diff --git a/packages/sdk/src/workflow-builder-root/workflow-builder-root.spec.tsx b/packages/sdk/src/workflow-builder-root/workflow-builder-root.spec.tsx index 20004832a..51598a716 100644 --- a/packages/sdk/src/workflow-builder-root/workflow-builder-root.spec.tsx +++ b/packages/sdk/src/workflow-builder-root/workflow-builder-root.spec.tsx @@ -14,7 +14,7 @@ // Heavy descendants (`RuntimeIntegrationWrapper`, `RootShell`, // `ReactFlowProvider`) are stubbed: the contract under test is the Root's own // lifecycle wiring, not what the children do. Without stubs the test -// transitively pulls `@synergycodes/overflow-ui`'s CSS side-effect import, +// transitively pulls `@workflowbuilder/ui`'s CSS side-effect import, // which vitest's node-side transformer can't process. import { act, render } from '@testing-library/react'; import type { ReactNode } from 'react'; diff --git a/packages/sdk/vite.config.mts b/packages/sdk/vite.config.mts index ce300d29f..73b5ad415 100644 --- a/packages/sdk/vite.config.mts +++ b/packages/sdk/vite.config.mts @@ -18,12 +18,18 @@ import svgr from 'vite-plugin-svgr'; // This list is INTENTIONALLY broader than `peerDependencies`. Only the // libraries a consumer touches directly (react, react-dom, @xyflow/react, // zustand) are peers; the rest (the JsonForms packages, immer, the -// i18next family) are regular `dependencies` so consumers don't have -// to install them by hand. Keeping the latter externalized (not bundled) -// still lets package managers dedupe them to a single copy via the caret -// ranges — bundling would instead hard-code a second copy and reintroduce -// the singleton hazards above. So: externalize all of them, but only -// declare the consumer-facing ones as peers. +// i18next family, @base-ui/react, @phosphor-icons/react) are regular +// `dependencies` so consumers don't have to install them by hand. Keeping +// the latter externalized (not bundled) still lets package managers dedupe +// them to a single copy via the caret ranges — bundling would instead +// hard-code a second copy and reintroduce the singleton hazards above. So: +// externalize all of them, but only declare the consumer-facing ones as +// peers. +// @base-ui/react and @phosphor-icons/react are here even though the SDK +// only imports @phosphor-icons/react directly — @base-ui/react is pulled +// in transitively through the bundled @workflowbuilder/ui and would +// otherwise get inlined a second time alongside the copy @workflowbuilder/ui +// (or the consumer app) already ships. // Anything not on this list (clsx, notistack, remeda, ace-builds, // react-ace, react-mentions-ts, ajv, …) is small enough or // SDK-internal enough that bundling is fine. @@ -38,6 +44,8 @@ const EXTERNAL_PACKAGES = [ 'react-i18next', 'immer', 'zustand', + '@base-ui/react', + '@phosphor-icons/react', ]; const isExternalPackage = (id: string) => @@ -56,9 +64,11 @@ export default defineConfig(({ command }) => ({ include: ['src/**/*'], exclude: ['src/**/*.spec.ts', 'src/**/*.spec.tsx', 'src/__shims__/**/*'], // Inline types from workspace-internal packages so external consumers - // don't need to install them. Icons is bundled into SDK's runtime - // (not externalized in Vite's build), and this keeps the types aligned. - bundledPackages: ['@workflow-builder/icons'], + // don't need to install them. Both packages are bundled into the SDK's + // runtime (not externalized in Vite's build), so their types must be + // inlined too - otherwise dist/index.d.ts would reference + // @workflowbuilder/ui, which the SDK does not declare as a dependency. + bundledPackages: ['@workflow-builder/icons', '@workflowbuilder/ui'], // The one type we can't reach from source alone — the ai-tools-control // depends on a few @jsonforms types we export for consumer convenience. insertTypesEntry: true, @@ -68,8 +78,6 @@ export default defineConfig(({ command }) => ({ alias: { '@/assets': path.resolve(import.meta.dirname, 'src/assets'), '@': path.resolve(import.meta.dirname, 'src'), - // overflow-ui doesn't export ./dist/index.css in its package.json exports field - 'overflow-ui-css': path.resolve(import.meta.dirname, 'node_modules/@synergycodes/overflow-ui/dist/index.css'), }, }, // Inline `process.env.NODE_ENV` at SDK build time. The SDK bundles deps diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index d366cdef9..6c3bc16fb 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -35,7 +35,7 @@ Workflow Builder SDK. affects the cascade; add `@workflowbuilder/ui/styles.css` once if you also want the global reset and typography. - **Dependencies**: `react` / `react-dom` are the only peer dependencies. - `@base-ui/react` (pinned to the validated `1.4.x` line) and + `@base-ui/react` (pinned to `1.7.0`) and `react-textarea-autosize` are regular dependencies; `date-fns`, `react-day-picker`, `clsx`, and the Phosphor icons are bundled into the package output. diff --git a/packages/ui/README.md b/packages/ui/README.md index dcc7c27a6..3ea8c2844 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -23,12 +23,11 @@ yarn add @workflowbuilder/ui ``` > **Peers & dependencies.** The only peer dependencies are `react` and -> `react-dom` (bring your own). `@base-ui/react` is a regular dependency pinned -> to the `1.4.x` line (later versions regressed dialog/menu/tooltip transitions), -> so it installs automatically — no need to add it yourself. The heavier -> component dependencies (date-fns, react-day-picker, clsx, Phosphor icons) are -> bundled into the package; `react-textarea-autosize` and `@base-ui/react` are -> the only ones resolved from your `node_modules`. +> `react-dom` (bring your own). `@base-ui/react` (pinned to `1.7.0`) is a regular +> dependency, so it installs automatically — no need to add it yourself. The +> heavier component dependencies (date-fns, react-day-picker, clsx, Phosphor +> icons) are bundled into the package; `react-textarea-autosize` and +> `@base-ui/react` are the only ones resolved from your `node_modules`. ### 🎨 Import styles diff --git a/packages/ui/css-layers.md b/packages/ui/css-layers.md index 4e478ed4b..8d3172566 100644 --- a/packages/ui/css-layers.md +++ b/packages/ui/css-layers.md @@ -36,6 +36,18 @@ stylesheets ignore `@import` entirely. leading with the statement, uses an unknown layer name, or ships rules outside `@layer`. +## One top-level layer across the ecosystem + +`ui` is deliberately the ONLY top-level layer, in this package and in the +SDK's `index.css` alike. Layer order is fixed by the first mention anywhere in +the document, and stylesheets here arrive through the JS module graph - so +with two independent order declarations (say `reset, ext-lib, ui` in one file +and `ui.base, ui.component` in another) the effective order would depend on +which file parses first. Everything that must lose to component styles +(third-party CSS such as xyflow, the SDK's reset block) joins `ui.base` +instead of getting its own top-level layer, and every stylesheet opens with +the identical `@layer ui.base, ui.component;` statement. + ## Variable defaults `:root` blocks with variable defaults (`--ax-public-*` in component sources, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7765e7588..df575a746 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@base-ui/react': - specifier: 1.4.1 - version: 1.4.1 + specifier: 1.7.0 + version: 1.7.0 '@phosphor-icons/core': specifier: ^2.1.1 version: 2.1.1 @@ -30,6 +30,9 @@ catalogs: react-dom: specifier: 19.1.0 version: 19.1.0 + react-is: + specifier: 19.1.0 + version: 19.1.0 importers: @@ -101,15 +104,24 @@ importers: apps/ai-studio: dependencies: + '@base-ui/react': + specifier: 'catalog:' + version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@jsonforms/core': + specifier: ^3.4.1 + version: 3.5.1 + '@jsonforms/react': + specifier: ^3.4.1 + version: 3.5.1(@jsonforms/core@3.5.1)(react@19.1.0) '@phosphor-icons/react': specifier: ^2.1.7 version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@synergycodes/overflow-ui': - specifier: 1.0.0-beta.27 - version: 1.0.0-beta.27(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@workflow-builder/types': specifier: workspace:* version: link:../../packages/types + '@workflowbuilder/ui': + specifier: workspace:* + version: link:../../packages/ui '@xyflow/react': specifier: 'catalog:' version: 12.10.0(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -126,7 +138,7 @@ importers: specifier: ^11.15.0 version: 11.16.0 react: - specifier: ^19.1.0 + specifier: 'catalog:' version: 19.1.0 react-dom: specifier: 'catalog:' @@ -134,12 +146,15 @@ importers: react-i18next: specifier: ^15.4.1 version: 15.4.1(i18next@24.2.3(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react-is: + specifier: 'catalog:' + version: 19.1.0 react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.1.8)(react@19.1.0) recharts: specifier: ^3.9.0 - version: 3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1) + version: 3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.1.0)(react@19.1.0)(redux@5.0.1) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -217,15 +232,24 @@ importers: apps/demo: dependencies: + '@base-ui/react': + specifier: 'catalog:' + version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@jsonforms/core': + specifier: ^3.4.1 + version: 3.5.1 + '@jsonforms/react': + specifier: ^3.4.1 + version: 3.5.1(@jsonforms/core@3.5.1)(react@19.1.0) '@microsoft/clarity': specifier: ^1.0.0 version: 1.0.2 '@phosphor-icons/react': specifier: ^2.1.7 version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@synergycodes/overflow-ui': - specifier: 1.0.0-beta.27 - version: 1.0.0-beta.27(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@workflowbuilder/ui': + specifier: workspace:* + version: link:../../packages/ui '@xyflow/react': specifier: 'catalog:' version: 12.10.0(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -254,7 +278,7 @@ importers: specifier: 0.4.0-beta.1 version: 0.4.0-beta.1 react: - specifier: ^19.1.0 + specifier: 'catalog:' version: 19.1.0 react-dom: specifier: 'catalog:' @@ -444,6 +468,9 @@ importers: packages/sdk: dependencies: + '@base-ui/react': + specifier: 'catalog:' + version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@cfworker/json-schema': specifier: 4.1.1 version: 4.1.1 @@ -459,9 +486,6 @@ importers: '@phosphor-icons/react': specifier: ^2.1.7 version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@synergycodes/overflow-ui': - specifier: 1.0.0-beta.27 - version: 1.0.0-beta.27(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@xyflow/react': specifier: ^12.0.0 version: 12.10.0(@types/react@19.1.8)(immer@10.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -523,6 +547,9 @@ importers: '@workflow-builder/types': specifier: workspace:* version: link:../types + '@workflowbuilder/ui': + specifier: workspace:* + version: link:../ui eslint-plugin-react: specifier: 7.37.2 version: 7.37.2(eslint@9.19.0(jiti@2.6.1)) @@ -572,7 +599,7 @@ importers: dependencies: '@base-ui/react': specifier: 'catalog:' - version: 1.4.1(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 1.7.0(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@phosphor-icons/react': specifier: 'catalog:' version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -888,8 +915,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.4.1': - resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} + '@base-ui/react@1.7.0': + resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==} engines: {node: '>=14.0.0'} peerDependencies: '@date-fns/tz': ^1.2.0 @@ -905,8 +932,8 @@ packages: date-fns: optional: true - '@base-ui/utils@0.2.8': - resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} + '@base-ui/utils@0.3.2': + resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==} peerDependencies: '@types/react': ^17 || ^18 || ^19 react: ^17 || ^18 || ^19 @@ -1147,60 +1174,6 @@ packages: '@emnapi/wasi-threads@1.0.2': resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@emotion/is-prop-valid@1.3.1': - resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/styled@11.14.1': - resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -1713,41 +1686,20 @@ packages: '@expressive-code/plugin-text-markers@0.41.7': resolution: {integrity: sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==} - '@floating-ui/core@1.6.9': - resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} - - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - - '@floating-ui/dom@1.6.13': - resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@floating-ui/react-dom@2.1.2': - resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/react@0.26.28': - resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - - '@floating-ui/utils@0.2.9': - resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} '@fontsource/poppins@5.2.7': resolution: {integrity: sha512-6uQyPmseo4FgI97WIhA4yWRlNaoLk4vSDK/PyRwdqqZb5zAEuc+Kunt8JTMcsHYUEGYBtN15SNkMajMdqUSUmg==} @@ -2115,27 +2067,6 @@ packages: peerDependencies: tslib: '2' - '@mantine/core@7.17.8': - resolution: {integrity: sha512-42sfdLZSCpsCYmLCjSuntuPcDg3PLbakSmmYfz5Auea8gZYLr+8SS5k647doVu0BRAecqYOytkX2QC5/u/8VHw==} - peerDependencies: - '@mantine/hooks': 7.17.8 - react: ^18.x || ^19.x - react-dom: ^18.x || ^19.x - - '@mantine/dates@7.17.8': - resolution: {integrity: sha512-KYog/YL83PnsMef7EZagpOFq9I2gfnK0eYSzC8YvV9Mb6t/x9InqRssGWVb0GIr+TNILpEkhKoGaSKZNy10Q1g==} - peerDependencies: - '@mantine/core': 7.17.8 - '@mantine/hooks': 7.17.8 - dayjs: '>=1.0.0' - react: ^18.x || ^19.x - react-dom: ^18.x || ^19.x - - '@mantine/hooks@7.17.8': - resolution: {integrity: sha512-96qygbkTjRhdkzd5HDU8fMziemN/h758/EwrFu7TlWrEP10Vw076u+Ap/sG6OT4RGPZYYoHrTlT+mkCZblWHuw==} - peerDependencies: - react: ^18.x || ^19.x - '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -2170,98 +2101,6 @@ packages: '@microsoft/tsdoc@0.16.0': resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} - '@mui/base@5.0.0-beta.62': - resolution: {integrity: sha512-TzJLCNlrMkSU4bTCdTT+TVUiGx4sjZLhH673UV6YN+rNNP8wJpkWfRSvjDB5HcbH2T0lUamnz643ZnV+8IiMjw==} - engines: {node: '>=14.0.0'} - deprecated: This package has been replaced by @base-ui/react - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/core-downloads-tracker@6.5.0': - resolution: {integrity: sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==} - - '@mui/material@6.5.0': - resolution: {integrity: sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^6.5.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/material-pigment-css': - optional: true - '@types/react': - optional: true - - '@mui/private-theming@6.4.9': - resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/styled-engine@6.5.0': - resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - - '@mui/system@6.5.0': - resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - - '@mui/types@7.2.24': - resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/utils@6.4.9': - resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@napi-rs/wasm-runtime@0.2.10': resolution: {integrity: sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==} @@ -2407,9 +2246,6 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -2817,12 +2653,6 @@ packages: '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} - '@synergycodes/overflow-ui@1.0.0-beta.27': - resolution: {integrity: sha512-Z8wDSlphLvLOA0JtzwFLrH0pO2PgmLwmG4OAmPewSh+Yet0L5dNNhWrs1AkuxkIxky74l+0Y11RVczLnuIGrAg==} - peerDependencies: - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tabby_ai/hijri-converter@1.0.5': resolution: {integrity: sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==} engines: {node: '>=16.0.0'} @@ -3095,12 +2925,6 @@ packages: '@types/pako@2.0.4': resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - - '@types/prop-types@15.7.14': - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -3115,11 +2939,6 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react-transition-group@4.4.12': - resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} - peerDependencies: - '@types/react': '*' - '@types/react@19.1.8': resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} @@ -3606,10 +3425,6 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -3973,9 +3788,6 @@ packages: engines: {node: '>=18'} hasBin: true - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -4014,10 +3826,6 @@ packages: cosmiconfig: '>=9' typescript: '>=5' - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -4358,9 +4166,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -4396,9 +4201,6 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -4918,9 +4720,6 @@ packages: resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} engines: {node: '>= 0.8'} - find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - find-up-simple@1.0.1: resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} engines: {node: '>=18'} @@ -5033,10 +4832,6 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -5222,9 +5017,6 @@ packages: resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==} engines: {node: '>=10.0.0'} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.12.14: resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} engines: {node: '>=16.9.0'} @@ -6674,8 +6466,8 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-is@19.1.0: + resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -6693,12 +6485,6 @@ packages: react-dom: '>=19.0.0' tailwind-merge: '>=3.0.0' - react-number-format@5.4.4: - resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==} - peerDependencies: - react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-redux@9.3.0: resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} peerDependencies: @@ -6715,48 +6501,12 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - react-textarea-autosize@8.5.9: resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==} engines: {node: '>=10'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-transition-group@4.4.5: - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -7172,10 +6922,6 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -7313,9 +7059,6 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} @@ -7364,9 +7107,6 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@3.5.0: resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} @@ -7738,16 +7478,6 @@ packages: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - use-composed-ref@1.4.0: resolution: {integrity: sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==} peerDependencies: @@ -7775,21 +7505,6 @@ packages: '@types/react': optional: true - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sync-external-store@1.4.0: - resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -8179,10 +7894,6 @@ packages: resolution: {integrity: sha512-9F3myNmJzUN/679jycdMxqtydPSDRAarSj3wPiF7pchEPnO9Dg07Oc+gIYLqXR4L+g+FSEVXXv2+mr54StLFOg==} hasBin: true - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - yaml@2.7.1: resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} engines: {node: '>= 14'} @@ -8648,12 +8359,12 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@base-ui/react@1.4.1(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@base-ui/react@1.7.0(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@floating-ui/react-dom': 2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@floating-ui/utils': 0.2.11 + '@base-ui/utils': 0.3.2(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/react-dom': 2.1.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.12 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) use-sync-external-store: 1.6.0(react@19.1.0) @@ -8662,10 +8373,10 @@ snapshots: '@types/react': 19.1.8 date-fns: 4.1.0 - '@base-ui/utils@0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@base-ui/utils@0.3.2(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@babel/runtime': 7.29.7 - '@floating-ui/utils': 0.2.11 + '@floating-ui/utils': 0.2.12 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) reselect: 5.2.0 @@ -9043,89 +8754,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.29.7 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/hash@0.9.2': {} - - '@emotion/is-prop-valid@1.3.1': - dependencies: - '@emotion/memoize': 0.9.0 - - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.8 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.1.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.0) - '@emotion/utils': 1.4.2 - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.8 - transitivePeerDependencies: - - supports-color - - '@emotion/unitless@0.10.0': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} - '@esbuild-kit/core-utils@3.3.2': dependencies: esbuild: 0.18.20 @@ -9439,47 +9067,22 @@ snapshots: dependencies: '@expressive-code/core': 0.41.7 - '@floating-ui/core@1.6.9': + '@floating-ui/core@1.8.0': dependencies: - '@floating-ui/utils': 0.2.9 + '@floating-ui/utils': 0.2.12 - '@floating-ui/core@1.7.5': + '@floating-ui/dom@1.8.0': dependencies: - '@floating-ui/utils': 0.2.11 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - '@floating-ui/dom@1.6.13': + '@floating-ui/react-dom@2.1.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/core': 1.6.9 - '@floating-ui/utils': 0.2.9 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - - '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/dom': 1.6.13 + '@floating-ui/dom': 1.8.0 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@floating-ui/react-dom@2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/dom': 1.7.6 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@floating-ui/utils': 0.2.9 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - tabbable: 6.2.0 - - '@floating-ui/utils@0.2.11': {} - - '@floating-ui/utils@0.2.9': {} + '@floating-ui/utils@0.2.12': {} '@fontsource/poppins@5.2.7': {} @@ -9848,33 +9451,6 @@ snapshots: '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) tslib: 2.8.1 - '@mantine/core@7.17.8(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mantine/hooks': 7.17.8(react@19.1.0) - clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-number-format: 5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) - react-textarea-autosize: 8.5.9(@types/react@19.1.8)(react@19.1.0) - type-fest: 4.33.0 - transitivePeerDependencies: - - '@types/react' - - '@mantine/dates@7.17.8(@mantine/core@7.17.8(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@mantine/core': 7.17.8(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mantine/hooks': 7.17.8(react@19.1.0) - clsx: 2.1.1 - dayjs: 1.11.21 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@mantine/hooks@7.17.8(react@19.1.0)': - dependencies: - react: 19.1.0 - '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.29.7 @@ -9884,7 +9460,7 @@ snapshots: '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -9971,97 +9547,6 @@ snapshots: '@microsoft/tsdoc@0.16.0': {} - '@mui/base@5.0.0-beta.62(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mui/types': 7.2.24(@types/react@19.1.8) - '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) - '@popperjs/core': 2.11.8 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.8 - - '@mui/core-downloads-tracker@6.5.0': {} - - '@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/core-downloads-tracker': 6.5.0 - '@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) - '@mui/types': 7.2.24(@types/react@19.1.8) - '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) - '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.1.8) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-is: 19.0.0 - react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) - '@types/react': 19.1.8 - - '@mui/private-theming@6.4.9(@types/react@19.1.8)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) - prop-types: 15.8.1 - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.8 - - '@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.1.0 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) - - '@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/private-theming': 6.4.9(@types/react@19.1.8)(react@19.1.0) - '@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(react@19.1.0) - '@mui/types': 7.2.24(@types/react@19.1.8) - '@mui/utils': 6.4.9(@types/react@19.1.8)(react@19.1.0) - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 19.1.0 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.1.8)(react@19.1.0) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) - '@types/react': 19.1.8 - - '@mui/types@7.2.24(@types/react@19.1.8)': - optionalDependencies: - '@types/react': 19.1.8 - - '@mui/utils@6.4.9(@types/react@19.1.8)(react@19.1.0)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/types': 7.2.24(@types/react@19.1.8) - '@types/prop-types': 15.7.14 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.1.0 - react-is: 19.0.0 - optionalDependencies: - '@types/react': 19.1.8 - '@napi-rs/wasm-runtime@0.2.10': dependencies: '@emnapi/core': 1.4.3 @@ -10163,8 +9648,6 @@ snapshots: '@pkgr/core@0.2.9': {} - '@popperjs/core@2.11.8': {} - '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -10513,26 +9996,6 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@synergycodes/overflow-ui@1.0.0-beta.27(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) - '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mantine/core': 7.17.8(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mantine/dates': 7.17.8(@mantine/core@7.17.8(@mantine/hooks@7.17.8(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@7.17.8(react@19.1.0))(dayjs@1.11.21)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mui/base': 5.0.0-beta.62(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@phosphor-icons/react': 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - transitivePeerDependencies: - - '@emotion/react' - - '@mantine/hooks' - - '@mui/material-pigment-css' - - '@types/react' - - dayjs - - supports-color - '@tabby_ai/hijri-converter@1.0.5': {} '@temporalio/activity@1.16.0': @@ -10889,10 +10352,6 @@ snapshots: '@types/pako@2.0.4': {} - '@types/parse-json@4.0.2': {} - - '@types/prop-types@15.7.14': {} - '@types/qs@6.14.0': {} '@types/raf@3.4.3': @@ -10904,10 +10363,6 @@ snapshots: dependencies: '@types/react': 19.1.8 - '@types/react-transition-group@4.4.12(@types/react@19.1.8)': - dependencies: - '@types/react': 19.1.8 - '@types/react@19.1.8': dependencies: csstype: 3.1.3 @@ -11644,12 +11099,6 @@ snapshots: axobject-query@4.1.0: {} - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.29.7 - cosmiconfig: 7.1.0 - resolve: 1.22.10 - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -11794,7 +11243,7 @@ snapshots: canvg@3.0.11: dependencies: - '@babel/runtime': 7.27.0 + '@babel/runtime': 7.29.7 '@types/raf': 3.4.3 core-js: 3.46.0 raf: 3.4.1 @@ -12015,8 +11464,6 @@ snapshots: '@simple-libs/stream-utils': 1.2.0 meow: 13.2.0 - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -12049,14 +11496,6 @@ snapshots: jiti: 2.6.1 typescript: 5.6.3 - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.0 @@ -12409,8 +11848,6 @@ snapshots: detect-libc@2.1.2: optional: true - detect-node-es@1.1.0: {} - deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 @@ -12439,11 +11876,6 @@ snapshots: dom-accessibility-api@0.5.16: {} - dom-helpers@5.2.1: - dependencies: - '@babel/runtime': 7.29.7 - csstype: 3.1.3 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -13117,8 +12549,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-root@1.1.0: {} - find-up-simple@1.0.1: {} find-up@4.1.0: @@ -13249,8 +12679,6 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-nonce@1.0.1: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -13566,10 +12994,6 @@ snapshots: heap-js@2.7.1: {} - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - hono@4.12.14: {} html-encoding-sniffer@4.0.0: @@ -15367,7 +14791,7 @@ snapshots: react-is@17.0.2: {} - react-is@19.0.0: {} + react-is@19.1.0: {} react-markdown@10.1.0(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -15395,11 +14819,6 @@ snapshots: react-dom: 19.1.0(react@19.1.0) tailwind-merge: 3.5.0 - react-number-format@5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react-redux@9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 @@ -15411,33 +14830,6 @@ snapshots: react-refresh@0.14.2: {} - react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): - dependencies: - react: 19.1.0 - react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.1.8 - - react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): - dependencies: - react: 19.1.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0) - react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) - use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.8 - - react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): - dependencies: - get-nonce: 1.0.1 - react: 19.1.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.1.8 - react-textarea-autosize@8.5.9(@types/react@19.1.8)(react@19.1.0): dependencies: '@babel/runtime': 7.27.0 @@ -15447,15 +14839,6 @@ snapshots: transitivePeerDependencies: - '@types/react' - react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@babel/runtime': 7.29.7 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - react@19.1.0: {} read-yaml-file@1.1.0: @@ -15473,7 +14856,7 @@ snapshots: readdirp@5.0.0: {} - recharts@3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.0.0)(react@19.1.0)(redux@5.0.1): + recharts@3.10.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.1.0)(react@19.1.0)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1))(react@19.1.0) clsx: 2.1.1 @@ -15483,7 +14866,7 @@ snapshots: immer: 11.1.15 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - react-is: 19.0.0 + react-is: 19.1.0 react-redux: 9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1) reselect: 5.2.0 tiny-invariant: 1.3.3 @@ -16052,8 +15435,6 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map@0.5.7: {} - source-map@0.6.1: {} source-map@0.7.6: {} @@ -16224,8 +15605,6 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - stylis@4.2.0: {} - stylis@4.4.0: {} suf-log@2.5.3: @@ -16279,8 +15658,6 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - tabbable@6.2.0: {} - tailwind-merge@3.5.0: {} tapable@2.3.2: {} @@ -16602,13 +15979,6 @@ snapshots: punycode: 1.4.1 qs: 6.14.0 - use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): - dependencies: - react: 19.1.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.1.8 - use-composed-ref@1.4.0(@types/react@19.1.8)(react@19.1.0): dependencies: react: 19.1.0 @@ -16628,18 +15998,6 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 - use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): - dependencies: - detect-node-es: 1.1.0 - react: 19.1.0 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.1.8 - - use-sync-external-store@1.4.0(react@19.1.0): - dependencies: - react: 19.1.0 - use-sync-external-store@1.6.0(react@19.1.0): dependencies: react: 19.1.0 @@ -17120,8 +16478,6 @@ snapshots: vscode-uri: 3.1.0 yaml: 2.7.1 - yaml@1.10.2: {} - yaml@2.7.1: {} yaml@2.8.4: {} @@ -17185,7 +16541,7 @@ snapshots: zustand@4.5.6(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0): dependencies: - use-sync-external-store: 1.4.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) optionalDependencies: '@types/react': 19.1.8 immer: 10.1.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index acb41cb41..f0db055d7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,10 +3,10 @@ packages: - './packages/*' catalog: - # Pinned to the version the component library was validated against. Base UI - # changed Dialog/Popup transition behaviour after 1.4.x (1.6 broke the modal - # backdrop fade), so bump only after re-checking modal/menu/tooltip transitions. - '@base-ui/react': 1.4.1 + # Exact-pinned: the keep-mounted modal relies on Base UI's transition + # lifecycle, and this range ships to consumers. Bump = visual pass over + # overlay transitions. + '@base-ui/react': 1.7.0 '@phosphor-icons/core': ^2.1.1 '@phosphor-icons/react': ^2.1.7 '@xyflow/react': 12.10.0 @@ -16,6 +16,8 @@ catalog: 'react': 19.1.0 '@types/react': 19.1.8 'react-dom': 19.1.0 + # Must match react - bump together. + 'react-is': 19.1.0 'ajv': ^8.18.0 useNodeVersion: 22.12.0 From f4871b4a230fcaace48d5ec17a59d47c4eb6ba3d Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 14 Aug 2026 11:00:54 +0200 Subject: [PATCH 04/23] UI Library docs: page-per-component + source-generated tables (3/3) (#46) * feat(docs): add live @workflowbuilder/ui component gallery * docs: point UI-library references at @workflowbuilder/ui * feat(docs): expand UI Library into page-per-component reference * refactor(ui): export component prop types and add @default tags * feat(docs): generate UI Library props and CSS tables from source via TypeDoc * docs(ui): render examples in isolated fixed-size previews with card-style props/CSS tables Wrap every UI Library example in a shadow-DOM ComponentPreview so components are styled only by @workflowbuilder/ui (isolated from Starlight CSS), shown in a fixed 2:1 dotted preview box that matches the original Overflow UI docs. Collapse each example island to a single representative instance. Rework the generated Props and CSS-variable references from tables into card lists: props show a 'required' chip (required-first) instead of a line-wrapping '?' marker, with Type/Default/description rows; CSS variables group into Color/Size. Drop the now-unused example-frame styles. * docs(ui): add live previews to diagram-component pages Give the Edge, NodeIcon, NodeDescription and NodePanel pages the same shadow-DOM ComponentPreview the UI components use, rendering each as a standalone example (NodePanel compositions; EdgeLabel variants positioned relatively outside a canvas) for parity with the original Overflow UI docs. NodeAsPortWrapper stays props-only, matching the reference. Adds @phosphor-icons/react to the docs app for the example icons. * docs(ui): anchor the Status example to a positioned container Status is an absolutely-positioned corner badge; rendered standalone it had no positioned ancestor and floated to the wrong place. Wrap it in a relative box that stands in for the node/field it marks, so it sits in the top-right corner as intended. * docs(ui): @base-ui/react is no longer a peer dependency The UI Library overview told consumers to install @base-ui/react alongside the package. It is now a regular dependency that installs automatically; react and react-dom are the only peers. * ci(docs): build UI before docs in the deploy workflow @workflowbuilder/ui components and generate-ui-api.mjs's TypeDoc pass both need packages/ui/dist to exist; add a Build UI step ahead of Build docs, mirroring the existing Build SDK step. * fix(docs): harden the UI API generator and guard component coverage - Fix the generator's lint errors (renamed vars for clarity, imported node:process, top-level await with process.exitCode instead of process.exit/main().catch, matching tools/preflight.mjs's pattern). - Strip internal engineering notes (matching /missing token/i) out of CSS variable comments instead of rendering them on the public docs pages. - Treat an unresolved props type as fatal (process.exitCode = 1) instead of a warning, so a renamed/typo'd type can't silently ship an empty page. - Add collectVariantProps() to merge Button's discriminated-union variant props (Label/Icon/IconLabel) into one deduped table with per-variant notes, and switch the TypeDoc entry point from index.ts (resolve) to src/components (expand) so those variant-only prop types get full reflections. - Add a check-ui-component-coverage.mjs guard, wired into generate:ui-api, asserting every packages/ui/vite.config.mts componentEntries item has a matching COMPONENTS entry, so a new published component can't ship without a docs page. - Run generate:ui-api before astro check in typecheck, so the coverage guard and generator failures surface there too. The unrelated PropRow/CssVar -> PropertyRow/CssVariable renames in props-table.astro / css-variables-table.astro are a lint-driven cleanup in the same generator/docs-api area. * docs(ui): document Collapsible and Icon switch, align Styles guidance - Add a Collapsible page: intro, live example, Usage, a hand-authored Parts table for Collapsible.Button/Collapsible.Content (no exported prop type to key a generated table on, same precedent as NodePanel), and generated Props/CSS variables tables. List it in the components index. - Add an Icon switch section to the Switch page documenting IconSwitch (live example, Usage, generated Props/CSS variables tables). - Rewrite the overview's Styles section: importing from the package root auto-injects the layer order/reset/tokens setup, so only tokens.css is needed; the styles.css + tokens.css pair only applies to the per-component subpath-import path. Matches packages/ui/README.md and packages/ui/css-layers.md. Depends on the collapsible/icon-switch COMPONENTS entries and the Button-variant TypeDoc entry point switch landed in the generator commit. * fix(ui): correct segment-picker shape prop's @default doc tag The runtime default (shape = 'default' in the forwardRef destructure) was documented as @default '' in the TSDoc comment, which the UI Library docs render verbatim. * chore: sync pnpm-lock.yaml with workspace typescript resolution Resolves vite-plugin-dts, vite-plugin-svgr, and i18next/react-i18next to a single typescript@5.9.3 peer resolution instead of a stale mixed 5.6.3/5.9.3 set, matching --frozen-lockfile. * refactor(ui): drop redundant Partial around WithIcon in ModalProps * fix(docs): include shared prop types in TypeDoc entry points Helper types like WithIcon live in src/shared, outside the components entry tree, so they got no reflection and their members (Accordion's and Modal's icon prop) silently vanished from the generated tables. * fix(docs): load the ui stylesheet at document level for portalled previews Modal, Menu, Select, Tooltip and DatePicker portal their popups to body, outside the shadow roots that carry the preview styles - the popups rendered unstyled. The library CSS is fully layered with no reset, so loading it globally is safe for the Starlight theme. * docs: drop the stale import-order requirement from the ui setup page Every built stylesheet has carried the @layer order statement since the stamping fix, so 'import styles.css before any component' is no longer a correctness requirement; styles.css also ships no reset, only typography. * refactor(ui): export the full DatePicker props surface The docs generator reads the exported DatePickerProps, but the props the component actually accepts (value, defaultValue, placeholder, valueFormat, type, error) lived on an unexported local widening type - the generated table documented none of the props the page's own example uses. The widening moves into the exported type, TSDoc included. * fix(docs): document union and overload components through the variant merge SegmentPicker's discriminated union was flattened first-wins: the table claimed value is always required and typed defaultValue as never, contradicting the page's own controlled/uncontrolled prose. NavButton was generated from its flat base type, so children - the prop its page is about - never appeared. Both now list their variant prop types, and a variant's 'foo?: never' exclusion counts as the prop being absent there instead of polluting the merged type. * fix(docs): fail loudly on every silent-empty path in the ui-api pipeline Four holes of the same class - the tool swallowing a problem and shipping a plausible-looking page: - a moved/renamed component directory made extractCssVariables glob nothing and exit 0 (page claims 'no CSS variables') - an unknown slug in PropsTable/CssVariablesTable rendered the empty state instead of failing the build (typo = false page in production) - a first-party type hidden behind an unresolvable utility wrapper (Partial/Omit) dropped its props with no trace - the class that once got the library reshaped to suit the generator - findTypeByName picked the first of duplicate type names silently * ci: build the docs site on PRs that can break it Every docs gate (ui-api generator, component-coverage guard, astro page rendering) used to run for the first time at release-time deploy - a broken docs change merged green and surfaced weeks later as a red deploy. Path-filtered to docs/ui/tokens changes so unrelated PRs pay nothing. * docs(changeset): record the exported ui prop-type surface * docs: pin section index pages to the top of their sidebar groups The index pages sorted alphabetically inside their own groups (the 'UI Components' link sat 7th within UI Components). Order 0 plus an Overview label matches the plugins/nodes convention; link lists use exact component symbol names, and the section description gains sentence case. * fix(ui): export the NavButton variant prop types from the barrel The changeset announced them, but only NavBaseButtonProps left the package - the variant types lived in component files outside every barrel. Verified with a consumer-side tsc probe against dist. * docs(ui): add default tags for runtime defaults missing from the tables The generator reads defaults from JSDoc only; Tooltip placement, IconSwitch variant and EdgeLabel size/state/type had runtime defaults with no tag, so their table cells rendered empty. * fix(docs): stop marking variant-only props as globally required Absent variants were filtered out before the every() check, so SegmentPicker documented value (controlled) and defaultValue (uncontrolled) as simultaneously required - a call that cannot exist. Required now means required in every variant; the variant note says where a prop is required otherwise. * ci(docs): require an MDX page for every generated component entry The coverage guard only cross-checked vite entries against generator entries; an entry with no page rendering its slug passed silently. * ci(docs): widen the docs gate path filter starlight-typedoc reads packages/sdk sources, and root manifests plus the workflow file itself shape the build - none of them triggered the gate, so an SDK change could break the docs build unnoticed until deploy. * fix(docs): let oversized examples shrink or grow the preview stage The fixed 2/1 stage with overflow:hidden clipped wide examples (Snackbar) on narrow layouts. The ratio is now a preferred size: shadow-root children cap at the stage width and content restores the automatic minimum height. Also corrects the isolation comment - inherited typography crosses the shadow boundary by design. * docs: complete the stateful examples and describe Snackbar's scope Nine usage blocks called useState without importing it, so a copied example failed to compile. Snackbar now says it is purely presentational - no positioning, stacking, or auto-dismiss. * docs: align pages with what the code actually does NodeAsPortWrapper stretches the existing target handle's hit area and adds no highlight of its own - the page claimed the whole node becomes a highlighted port with no preconditions. The missing-token note now describes the comments as historical markers (several referenced tokens exist in the export today). Overview drops the 1.4.x pin claim, and the custom-node guide gains the missing ui package install step. * docs: fix the dead ui-library link and the headless heading /ui-library/ has no index page - the section entry is /ui-library/overview/. The 'Headless components' heading sat above a paragraph describing a fully styled library built on headless primitives. * fix(docs): scope and classify the generated CSS variable tables Subcomponents with their own page had their variables repeated on the parent: Button listed 17 of NavButton's, Switch 12 of IconSwitch's, and overriding them there does nothing. The Color/Size split now follows the value through the design tokens instead of guessing from the name, which put edge-stroke-width under Color and the snackbar status borders under Size. * refactor(docs): share the component list between generator and guard The guard regex-scraped COMPONENTS out of the generator source, so a change in quote style would yield an empty list and pass having checked nothing. Both now import the same module; the remaining scrape (the vite entry list, which is TypeScript) fails on an empty result. * style(docs): rename a loop variable flagged by the lint rule * fix(docs): follow interface references when collecting props The reference branch accepted only type aliases, so a prop type written as an interface would drop its members from the table with no warning - the entry point of the same function already handled both forms. * fix(docs): generate the last two hand-written API tables NodePanel had no generator entry: its CSS variable list was written by hand and covered 10 of the 20 variables in source. The useEdgeStyle parameter table was hand-written too, so its type is now exported and documented from source. An entry may carry dir: null when it documents an API but owns no stylesheet - the edge variables stay on the Edge page instead of splitting off to the hook. * docs: drop the private icons package from the NodeIcon example The snippet imported Icon from @workflow-builder/icons, which is marked private and cannot be installed from npm - a consumer copying it hits an unresolvable dependency. It now uses Phosphor, which ships with the package, and says that any icon library works. * fix(docs): say when a component forwards native attributes Input documented four props while its own example used placeholder, value and onChange - and no page said the component extends a native element's attributes. The generator now detects the forwarded element by walking the prop type (through Omit/DetailedHTMLProps wrappers and first-party aliases) and each affected page states it in one line, instead of the table listing ~280 DOM attributes or staying silent. * docs: say that example icons need installing The examples import @phosphor-icons/react. It is a dependency of the library, not something a consumer may import without declaring it - under pnpm's strict node_modules that import does not resolve. The install page now says so once, and the NodeIcon page points at it. * docs: drop the prose about icon libraries The example's import speaks for itself. * docs: drop the missing-token section The comments it describes are stripped from the generated tables and do not survive minification into dist, so a reader of the docs has no way to encounter them. * refactor(docs): trim inline comments to the traps worth keeping --------- Co-authored-by: Jan Librowski Co-authored-by: librowski-synergy Co-authored-by: librowski-synergy --- .changeset/ui-export-prop-types.md | 5 + .changeset/ui-export-use-edge-style-params.md | 5 + .github/workflows/deploy-docs.yml | 5 + .github/workflows/pr-check-docs.yml | 57 +++ .github/workflows/pr-check.yml | 6 +- .gitignore | 5 + apps/docs/astro.config.mjs | 25 +- apps/docs/package.json | 15 +- .../scripts/check-ui-component-coverage.mjs | 77 ++++ apps/docs/scripts/generate-ui-api.mjs | 419 ++++++++++++++++++ apps/docs/scripts/ui-components.mjs | 68 +++ .../components/api/css-variables-table.astro | 79 ++++ .../docs/src/components/api/props-table.astro | 128 ++++++ .../src/components/ui-examples/accordion.tsx | 13 + .../src/components/ui-examples/avatar.tsx | 17 + .../src/components/ui-examples/button.tsx | 11 + .../src/components/ui-examples/checkbox.tsx | 14 + .../components/ui-examples/collapsible.tsx | 14 + .../ui-examples/component-preview.module.css | 42 ++ .../ui-examples/component-preview.tsx | 38 ++ .../components/ui-examples/date-picker.tsx | 19 + apps/docs/src/components/ui-examples/edge.tsx | 31 ++ .../docs/src/components/ui-examples/input.tsx | 14 + apps/docs/src/components/ui-examples/menu.tsx | 20 + .../docs/src/components/ui-examples/modal.tsx | 29 ++ .../src/components/ui-examples/nav-button.tsx | 11 + .../ui-examples/node-description.tsx | 15 + .../src/components/ui-examples/node-icon.tsx | 17 + .../src/components/ui-examples/node-panel.tsx | 17 + .../docs/src/components/ui-examples/radio.tsx | 18 + .../components/ui-examples/segment-picker.tsx | 18 + .../src/components/ui-examples/select.tsx | 20 + .../src/components/ui-examples/separator.tsx | 15 + .../src/components/ui-examples/snackbar.tsx | 11 + .../src/components/ui-examples/status.tsx | 26 ++ .../src/components/ui-examples/switch.tsx | 25 ++ .../src/components/ui-examples/text-area.tsx | 14 + .../src/components/ui-examples/tooltip.tsx | 16 + apps/docs/src/content/docs/faq.md | 2 +- .../content/docs/guides/add-a-custom-node.mdx | 8 + .../src/content/docs/overview/architecture.md | 18 +- .../design-system-and-customization.mdx | 6 +- .../content/docs/ui-library/design-tokens.mdx | 75 ++++ .../ui-library/diagram-components/edge.mdx | 106 +++++ .../ui-library/diagram-components/index.mdx | 17 + .../node-as-port-wrapper.mdx | 54 +++ .../diagram-components/node-description.mdx | 39 ++ .../diagram-components/node-icon.mdx | 40 ++ .../diagram-components/node-panel.mdx | 77 ++++ .../src/content/docs/ui-library/overview.mdx | 69 +++ .../ui-library/ui-components/accordion.mdx | 37 ++ .../docs/ui-library/ui-components/avatar.mdx | 32 ++ .../docs/ui-library/ui-components/button.mdx | 34 ++ .../ui-library/ui-components/checkbox.mdx | 33 ++ .../ui-library/ui-components/collapsible.mdx | 51 +++ .../ui-library/ui-components/date-picker.mdx | 41 ++ .../docs/ui-library/ui-components/index.mdx | 30 ++ .../docs/ui-library/ui-components/input.mdx | 33 ++ .../docs/ui-library/ui-components/menu.mdx | 43 ++ .../docs/ui-library/ui-components/modal.mdx | 53 +++ .../ui-library/ui-components/nav-button.mdx | 33 ++ .../docs/ui-library/ui-components/radio.mdx | 33 ++ .../ui-components/segment-picker.mdx | 40 ++ .../docs/ui-library/ui-components/select.mdx | 42 ++ .../ui-library/ui-components/separator.mdx | 37 ++ .../ui-library/ui-components/snackbar.mdx | 37 ++ .../docs/ui-library/ui-components/status.mdx | 32 ++ .../docs/ui-library/ui-components/switch.mdx | 62 +++ .../ui-library/ui-components/text-area.mdx | 34 ++ .../docs/ui-library/ui-components/tooltip.mdx | 40 ++ .../ui/src/components/accordion/accordion.tsx | 1 + packages/ui/src/components/avatar/avatar.tsx | 5 +- packages/ui/src/components/button/index.ts | 2 + .../src/components/button/nav-button/types.ts | 8 + .../ui/src/components/checkbox/checkbox.tsx | 5 +- .../components/date-picker/date-picker.tsx | 50 +-- .../ui/src/components/date-picker/types.ts | 46 ++ .../components/edge/edge-label/edge-label.tsx | 9 +- .../edge/use-edge-styles/use-edge-styles.tsx | 2 +- packages/ui/src/components/input/types.ts | 2 + packages/ui/src/components/menu/menu.tsx | 2 +- packages/ui/src/components/modal/modal.tsx | 9 +- .../node-as-port-wrapper.tsx | 4 +- .../node-description/node-description.tsx | 4 +- .../components/node/node-icon/node-icon.tsx | 4 +- .../ui/src/components/radio-button/radio.tsx | 9 +- .../segment-picker/segment-picker.tsx | 13 +- packages/ui/src/components/select/select.tsx | 2 + .../ui/src/components/snackbar/snackbar.tsx | 1 + packages/ui/src/components/status/status.tsx | 4 +- .../switch/icon-switch/icon-switch.tsx | 3 + packages/ui/src/components/switch/switch.tsx | 1 + .../ui/src/components/text-area/text-area.tsx | 1 + .../ui/src/components/tooltip/tooltip.tsx | 5 +- pnpm-lock.yaml | 341 ++++++++++++-- 95 files changed, 3064 insertions(+), 136 deletions(-) create mode 100644 .changeset/ui-export-prop-types.md create mode 100644 .changeset/ui-export-use-edge-style-params.md create mode 100644 .github/workflows/pr-check-docs.yml create mode 100644 apps/docs/scripts/check-ui-component-coverage.mjs create mode 100644 apps/docs/scripts/generate-ui-api.mjs create mode 100644 apps/docs/scripts/ui-components.mjs create mode 100644 apps/docs/src/components/api/css-variables-table.astro create mode 100644 apps/docs/src/components/api/props-table.astro create mode 100644 apps/docs/src/components/ui-examples/accordion.tsx create mode 100644 apps/docs/src/components/ui-examples/avatar.tsx create mode 100644 apps/docs/src/components/ui-examples/button.tsx create mode 100644 apps/docs/src/components/ui-examples/checkbox.tsx create mode 100644 apps/docs/src/components/ui-examples/collapsible.tsx create mode 100644 apps/docs/src/components/ui-examples/component-preview.module.css create mode 100644 apps/docs/src/components/ui-examples/component-preview.tsx create mode 100644 apps/docs/src/components/ui-examples/date-picker.tsx create mode 100644 apps/docs/src/components/ui-examples/edge.tsx create mode 100644 apps/docs/src/components/ui-examples/input.tsx create mode 100644 apps/docs/src/components/ui-examples/menu.tsx create mode 100644 apps/docs/src/components/ui-examples/modal.tsx create mode 100644 apps/docs/src/components/ui-examples/nav-button.tsx create mode 100644 apps/docs/src/components/ui-examples/node-description.tsx create mode 100644 apps/docs/src/components/ui-examples/node-icon.tsx create mode 100644 apps/docs/src/components/ui-examples/node-panel.tsx create mode 100644 apps/docs/src/components/ui-examples/radio.tsx create mode 100644 apps/docs/src/components/ui-examples/segment-picker.tsx create mode 100644 apps/docs/src/components/ui-examples/select.tsx create mode 100644 apps/docs/src/components/ui-examples/separator.tsx create mode 100644 apps/docs/src/components/ui-examples/snackbar.tsx create mode 100644 apps/docs/src/components/ui-examples/status.tsx create mode 100644 apps/docs/src/components/ui-examples/switch.tsx create mode 100644 apps/docs/src/components/ui-examples/text-area.tsx create mode 100644 apps/docs/src/components/ui-examples/tooltip.tsx create mode 100644 apps/docs/src/content/docs/ui-library/design-tokens.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/edge.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/index.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/node-as-port-wrapper.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/node-description.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/node-icon.mdx create mode 100644 apps/docs/src/content/docs/ui-library/diagram-components/node-panel.mdx create mode 100644 apps/docs/src/content/docs/ui-library/overview.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/accordion.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/avatar.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/button.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/checkbox.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/collapsible.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/date-picker.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/index.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/input.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/menu.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/modal.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/nav-button.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/radio.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/segment-picker.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/select.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/separator.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/snackbar.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/status.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/switch.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/text-area.mdx create mode 100644 apps/docs/src/content/docs/ui-library/ui-components/tooltip.mdx diff --git a/.changeset/ui-export-prop-types.md b/.changeset/ui-export-prop-types.md new file mode 100644 index 000000000..383d256fe --- /dev/null +++ b/.changeset/ui-export-prop-types.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +Component prop types are now exported: `AvatarProps`, `CheckboxProps`, `RadioProps`, `StatusProps`, `TooltipProps`, `MenuProps`, `ModalProps`, `EdgeLabelProps`, `NodeIconProps`, `NodeDescriptionProps`, `NodeAsPortWrapperProps`, `SegmentPickerProps` (with its controlled/uncontrolled variants), the NavButton variant prop types, and `DatePickerProps` now covers the component's full runtime surface (`value`, `defaultValue`, `placeholder`, `valueFormat`, `type`, `error`). Supporting types used in those signatures (`Shape`, `IconNode`) are exported as well. diff --git a/.changeset/ui-export-use-edge-style-params.md b/.changeset/ui-export-use-edge-style-params.md new file mode 100644 index 000000000..31e85cb50 --- /dev/null +++ b/.changeset/ui-export-use-edge-style-params.md @@ -0,0 +1,5 @@ +--- +'@workflowbuilder/ui': minor +--- + +`UseEdgeStyleParams`, the parameter type of the `useEdgeStyle` hook, is now exported. diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 7d55e66bc..4cd7d557c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -42,6 +42,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + # UI must build first: docs imports @workflowbuilder/ui/{styles,tokens}.css + # and its components, both resolved from packages/ui/dist. + - name: Build UI + run: pnpm build:ui + # SDK must build first so TypeDoc can read its compiled types. - name: Build SDK run: pnpm --filter @workflowbuilder/sdk build:lib diff --git a/.github/workflows/pr-check-docs.yml b/.github/workflows/pr-check-docs.yml new file mode 100644 index 000000000..209e190a2 --- /dev/null +++ b/.github/workflows/pr-check-docs.yml @@ -0,0 +1,57 @@ +name: PR Check (docs) + +# The docs site is generated from packages/ui source (ui-api tables, live +# examples) and consumes its built dist, so a PR touching either can break +# the docs build - which otherwise first runs at release-time deploy. +# Full `astro build` is the gate: it runs the ui-api generator, the +# component-coverage guard, and page rendering (loud failures included). +# Typecheck is deliberately absent: apps/docs tolerates known starlight +# virtual-module type errors. + +on: + pull_request: + branches: + - main + paths: + - 'apps/docs/**' + - 'packages/ui/**' + - 'packages/tokens/**' + # starlight-typedoc generates the /api reference from SDK sources. + - 'packages/sdk/**' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'package.json' + - '.github/workflows/pr-check-docs.yml' + +permissions: + contents: read + +jobs: + docs: + name: Docs build (ui-api generator + coverage + astro) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Enable Corepack + run: npm i -g corepack@latest + + - name: Install pnpm + run: corepack prepare + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build UI library + run: pnpm build:ui + + - name: Build docs + run: pnpm build:docs diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 844b4d0df..f1b4dc3c2 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -3,9 +3,9 @@ name: PR Check # Runs on every PR to give reviewers fast feedback on whether the change # is safe to merge. Focus on the published packages @workflowbuilder/sdk and # @workflowbuilder/ui (plus its private @workflowbuilder/ui-tokens build) + -# global format consistency. Other workspaces (apps/docs, demo, ai-studio, …) -# are not checked here — they're internal and have their own broken-state -# tolerances (e.g. starlight virtual-module type errors). +# global format consistency. apps/docs has its own path-filtered workflow +# (pr-check-docs.yml); demo and ai-studio are not checked here — they're +# internal and have their own broken-state tolerances. on: pull_request: diff --git a/.gitignore b/.gitignore index 3c2d2a3a0..f0626771d 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,8 @@ CLAUDE.local.md # and is copied into this dir by a tiny Astro integration after typedoc # generation (see astro.config.mjs). apps/docs/src/content/docs/api/ + +# UI Library props + CSS-variable data, generated from @workflowbuilder/ui by +# apps/docs/scripts/generate-ui-api.mjs (TypeDoc + CSS extraction) on every +# docs build / dev. Source of truth is the library, so keep it out of git. +apps/docs/src/generated/ diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs index 599ee9638..0453e265c 100644 --- a/apps/docs/astro.config.mjs +++ b/apps/docs/astro.config.mjs @@ -1,6 +1,7 @@ import { copyFileSync, mkdirSync } from 'node:fs'; import path from 'node:path'; +import react from '@astrojs/react'; import starlight from '@astrojs/starlight'; import umami from '@yeskunall/astro-umami'; import { defineConfig, passthroughImageService } from 'astro/config'; @@ -70,6 +71,7 @@ export default defineConfig({ }, integrations: [ icon(), + react(), umami({ id: UMAMI_WEBSITE_ID }), starlight({ plugins: [ @@ -113,7 +115,19 @@ export default defineConfig({ }, }), ], - customCss: ['./src/styles/custom.css'], + // `@workflowbuilder/ui` styles are safe to load globally: everything is + // layered (no global reset), classes are hashed or opt-in, and tokens.css + // only defines `--ax-*` custom properties keyed on `html[data-theme]` — + // which Starlight already toggles, so the live component showcases follow + // the docs light/dark theme. index.css is required at document level: + // Modal/Menu/Select/Tooltip/DatePicker portal their popups to body, + // outside the shadow roots that carry the preview styles. + customCss: [ + './src/styles/custom.css', + '@workflowbuilder/ui/styles.css', + '@workflowbuilder/ui/tokens.css', + '@workflowbuilder/ui/index.css', + ], components: { Head: './src/components/head.astro', Search: './src/components/search.astro', @@ -159,6 +173,15 @@ export default defineConfig({ { label: 'Node Schemas', autogenerate: { directory: 'node-schemas' } }, { label: 'Built-in Nodes', autogenerate: { directory: 'nodes' } }, { label: 'Plugins', autogenerate: { directory: 'plugins' } }, + { + label: 'UI Library', + items: [ + { label: 'Overview', link: '/ui-library/overview/' }, + { label: 'Design tokens', link: '/ui-library/design-tokens/' }, + { label: 'UI Components', autogenerate: { directory: 'ui-library/ui-components' } }, + { label: 'Diagram Components', autogenerate: { directory: 'ui-library/diagram-components' } }, + ], + }, // API Reference — pages auto-generated by `starlight-typedoc` from // packages/sdk's barrel into `src/content/docs/api//`. // Folder names match the `@category` tag in source TSDoc verbatim. diff --git a/apps/docs/package.json b/apps/docs/package.json index 2ace85e85..2e643249c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -4,28 +4,37 @@ "private": true, "type": "module", "scripts": { - "dev": "pnpm clean:typedoc && astro dev", - "build": "pnpm clean:typedoc && node scripts/check-sidebar-categories.mjs && astro build && node scripts/touch-distribution-index.mjs && node scripts/copy-swa-config.mjs", + "dev": "pnpm clean:typedoc && pnpm generate:ui-api && astro dev", + "build": "pnpm clean:typedoc && pnpm generate:ui-api && node scripts/check-sidebar-categories.mjs && astro build && node scripts/touch-distribution-index.mjs && node scripts/copy-swa-config.mjs", + "generate:ui-api": "node scripts/generate-ui-api.mjs && node scripts/check-ui-component-coverage.mjs", "clean:typedoc": "node -e \"import('node:fs').then(fs => fs.rmSync('src/content/docs/api', { recursive: true, force: true }))\"", "preview": "astro preview", - "typecheck": "astro check", + "typecheck": "pnpm generate:ui-api && astro check", "format": "prettier --write --log-level silent \"**/*.astro\"", "lint": "eslint .", "lint:fix": "eslint . --fix" }, "dependencies": { + "@astrojs/react": "^4.4.0", "@astrojs/starlight": "^0.37.6", + "@base-ui/react": "catalog:", "@iconify-json/ph": "^1.2.2", + "@phosphor-icons/react": "catalog:", "@workflowbuilder/sdk": "workspace:*", + "@workflowbuilder/ui": "workspace:*", "@yeskunall/astro-umami": "^0.0.7", "astro": "^5.18.0", "astro-icon": "^1.1.5", "marked": "^15.0.0", + "react": "catalog:", + "react-dom": "catalog:", "rehype-external-links": "^3.0.0", "starlight-image-zoom": "^0.13.2" }, "devDependencies": { "@astrojs/check": "^0.9.6", + "@types/react": "catalog:", + "@types/react-dom": "^19.1.0", "eslint-plugin-astro": "^1.3.1", "starlight-typedoc": "^0.21.3", "typedoc": "^0.28.9", diff --git a/apps/docs/scripts/check-ui-component-coverage.mjs b/apps/docs/scripts/check-ui-component-coverage.mjs new file mode 100644 index 000000000..6665c6b88 --- /dev/null +++ b/apps/docs/scripts/check-ui-component-coverage.mjs @@ -0,0 +1,77 @@ +// Cross-checks the documented component surface: every publishable subpath in +// packages/ui/vite.config.mts has a COMPONENTS entry, and every COMPONENTS +// entry is rendered by an MDX page. Without it a component ships to npm with +// no docs page and nothing in the build complains. Runs as part of +// `generate:ui-api`. + +import { globSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +import { COMPONENTS } from './ui-components.mjs'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const documentsRoot = path.resolve(here, '..'); +const repoRoot = path.resolve(documentsRoot, '../..'); +const viteConfigPath = path.resolve(repoRoot, 'packages/ui/vite.config.mts'); + +const NARRATIVE_ONLY = new Set([]); + +// Read as text, not imported - it is TypeScript. Empty means the shape changed. +function extractComponentEntries(source) { + const match = /const componentEntries = \[([\s\S]*?)] as const;/.exec(source); + if (!match) throw new Error('Could not find `componentEntries` in packages/ui/vite.config.mts'); + const entries = [...match[1].matchAll(/'([^']+)'/g)].map((m) => m[1]); + if (entries.length === 0) { + throw new Error('Extracted zero entries from `componentEntries` in packages/ui/vite.config.mts'); + } + return entries; +} + +const componentEntries = extractComponentEntries(readFileSync(viteConfigPath, 'utf8')); +const componentDirectories = COMPONENTS.map((component) => component.dir).filter(Boolean); +const componentSlugs = COMPONENTS.map((component) => component.slug); + +const missing = componentEntries.filter((entry) => { + if (NARRATIVE_ONLY.has(entry)) return false; + return !componentDirectories.some((directory) => directory === entry || directory.startsWith(`${entry}/`)); +}); + +const contentRoot = path.resolve(documentsRoot, 'src/content/docs'); +const pageSources = globSync('**/*.mdx', { cwd: contentRoot }).map((file) => + readFileSync(path.resolve(contentRoot, file), 'utf8'), +); +const unrendered = componentSlugs.filter( + (slug) => !pageSources.some((source) => source.includes(`slug="${slug}"`)), +); + +if (missing.length > 0) { + console.error('error: componentEntries in packages/ui/vite.config.mts have no matching COMPONENTS entry.\n'); + for (const entry of missing) { + console.error(` - ${entry}`); + } + console.error( + '\nAdd a COMPONENTS entry in apps/docs/scripts/generate-ui-api.mjs with a matching `dir`, ' + + 'or add the entry to NARRATIVE_ONLY in this script if it is deliberately prose-only.', + ); + process.exitCode = 1; +} + +if (unrendered.length > 0) { + console.error('error: COMPONENTS entries in generate-ui-api.mjs are rendered by no MDX page.\n'); + for (const slug of unrendered) { + console.error(` - ${slug}`); + } + console.error( + '\nAdd a docs page that uses / , ' + + 'or remove the generator entry.', + ); + process.exitCode = 1; +} + +if (missing.length === 0 && unrendered.length === 0) { + console.log( + `✓ component coverage ok — ${componentEntries.length} vite entries and ${componentSlugs.length} docs slugs cross-checked.`, + ); +} diff --git a/apps/docs/scripts/generate-ui-api.mjs b/apps/docs/scripts/generate-ui-api.mjs new file mode 100644 index 000000000..f641b21bc --- /dev/null +++ b/apps/docs/scripts/generate-ui-api.mjs @@ -0,0 +1,419 @@ +/* + * Generates `src/generated/ui-api.json` for the UI Library docs. + * + * Props are extracted with TypeDoc (source of truth: the component prop types + * in `@workflowbuilder/ui`); CSS variables are extracted from each component's + * stylesheets. The per-component docs pages render this JSON, so the Props and + * CSS variables tables never drift from source. Run by `pnpm generate:ui-api` + * and as a prebuild step in `dev` / `build`. + */ +import { execFile } from 'node:child_process'; +import { existsSync, globSync, readFileSync } from 'node:fs'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +import { COMPONENTS } from './ui-components.mjs'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const documentsRoot = path.resolve(here, '..'); +const repoRoot = path.resolve(documentsRoot, '../..'); +const uiSource = path.resolve(repoRoot, 'packages/ui/src'); +const outFile = path.resolve(documentsRoot, 'src/generated/ui-api.json'); +const tdJson = path.resolve(documentsRoot, 'node_modules/.cache/ui-typedoc.json'); + +// Engineering notes in the CSS, never public documentation. +const INTERNAL_NOTE_RE = /missing token/i; + +async function runTypedoc() { + await mkdir(path.dirname(tdJson), { recursive: true }); + const bin = path.resolve(documentsRoot, 'node_modules/.bin/typedoc'); + await promisify(execFile)( + bin, + [ + '--json', + tdJson, + // Whole tree, not the barrel: variant prop types are not re-exported. + '--entryPoints', + path.resolve(uiSource, 'components'), + // A type outside the entry tree gets no reflection and vanishes from the tables. + '--entryPoints', + path.resolve(uiSource, 'shared'), + '--entryPointStrategy', + 'expand', + '--tsconfig', + path.resolve(repoRoot, 'packages/ui/tsconfig.json'), + '--excludeExternals', + '--excludePrivate', + '--skipErrorChecking', + '--logLevel', + 'Error', + ], + { cwd: repoRoot, maxBuffer: 64 * 1024 * 1024 }, + ); + return JSON.parse(await readFile(tdJson, 'utf8')); +} + +function indexById(root) { + const byId = new Map(); + (function walk(node) { + if (node && typeof node.id === 'number') byId.set(node.id, node); + for (const child of node.children ?? []) walk(child); + })(root); + return byId; +} + +function findTypeByName(root, name, warnings) { + const matches = []; + (function walk(node) { + if (node.name === name && (node.kind === 2_097_152 || node.kind === 256)) matches.push(node); + for (const child of node.children ?? []) walk(child); + })(root); + if (matches.length > 1 && warnings) { + warnings.push(`type name "${name}" is ambiguous (${matches.length} declarations) - the table would document whichever TypeDoc emitted first`); + } + return matches[0] ?? null; +} + +function typeToString(t, byId, depth = 0) { + if (!t || depth > 6) return 'unknown'; + switch (t.type) { + case 'intrinsic': { + return t.name; + } + case 'literal': { + return typeof t.value === 'string' ? `'${t.value}'` : String(t.value); + } + case 'reference': { + const arguments_ = t.typeArguments?.length + ? `<${t.typeArguments.map((a) => typeToString(a, byId, depth + 1)).join(', ')}>` + : ''; + return `${t.name}${arguments_}`; + } + case 'union': { + return t.types.map((x) => typeToString(x, byId, depth + 1)).join(' | '); + } + case 'intersection': { + return t.types.map((x) => typeToString(x, byId, depth + 1)).join(' & '); + } + case 'array': { + return `${typeToString(t.elementType, byId, depth + 1)}[]`; + } + case 'tuple': { + return `[${(t.elements ?? []).map((x) => typeToString(x, byId, depth + 1)).join(', ')}]`; + } + case 'reflection': { + const sig = t.declaration?.signatures?.[0]; + if (sig) { + const params = (sig.parameters ?? []) + .map((p) => `${p.name}: ${typeToString(p.type, byId, depth + 1)}`) + .join(', '); + return `(${params}) => ${typeToString(sig.type, byId, depth + 1)}`; + } + return '{ … }'; + } + case 'indexedAccess': { + return `${typeToString(t.objectType, byId, depth + 1)}[${typeToString(t.indexType, byId, depth + 1)}]`; + } + case 'templateLiteral': { + return 'string'; + } + case 'query': { + return typeToString(t.queryType, byId, depth + 1); + } + case 'predicate': { + return 'boolean'; + } + case 'typeOperator': { + return `${t.operator} ${typeToString(t.target, byId, depth + 1)}`; + } + default: { + return t.name ?? 'unknown'; + } + } +} + +function summaryText(comment) { + if (!comment?.summary) return ''; + return comment.summary + .map((s) => s.text) + .join('') + .trim(); +} + +function defaultTag(comment) { + const tag = (comment?.blockTags ?? []).find((b) => b.tag === '@default' || b.tag === '@defaultValue'); + if (!tag) return null; + let value = tag.content + .map((c) => c.text) + .join('') + .trim(); + value = value + .replace(/^```[a-z]*\s*/i, '') + .replace(/\s*```$/, '') + .trim(); // strip ```ts … ``` fences + value = value.replaceAll(/^`+|`+$/g, '').trim(); // strip inline backticks + return value || null; +} + +// Which native element a component forwards its remaining props to; listing +// ~280 DOM attributes in the table would drown the props that are ours. +const NATIVE_ATTRIBUTE_TYPES = new Map([ + ['InputHTMLAttributes', 'input'], + ['ButtonHTMLAttributes', 'button'], + ['TextareaHTMLAttributes', 'textarea'], + ['SelectHTMLAttributes', 'select'], + ['AnchorHTMLAttributes', 'a'], + ['HTMLAttributes', 'element'], +]); + +function findNativeElement(typeNode, byId, depth = 0) { + if (!typeNode || depth > 8) return null; + if (typeNode.type === 'reference') { + const element = NATIVE_ATTRIBUTE_TYPES.get(typeNode.name.replace(/^React\./, '')); + if (element === 'element') { + const tag = /^HTML(\w*?)Element$/.exec(typeNode.typeArguments?.[0]?.name ?? '')?.[1]; + return tag ? tag.toLowerCase() || 'element' : 'element'; + } + if (element) return element; + if (typeof typeNode.target === 'number') { + const found = findNativeElement(byId.get(typeNode.target)?.type, byId, depth + 1); + if (found) return found; + } + } + for (const nested of [...(typeNode.types ?? []), ...(typeNode.typeArguments ?? [])]) { + const found = findNativeElement(nested, byId, depth + 1); + if (found) return found; + } + return null; +} + +// Own properties of a prop type, walking intersections and skipping native members. +function collectProps(typeNode, byId, accumulator = new Map(), context = null) { + if (!typeNode) return accumulator; + if (typeNode.kind === 2_097_152 || typeNode.kind === 256) { + if (typeNode.children?.length) { + for (const child of typeNode.children) addProperty(child, byId, accumulator); + return accumulator; + } + return collectProps(typeNode.type, byId, accumulator, context); + } + if (typeNode.type === 'intersection' || typeNode.type === 'union') { + for (const member of typeNode.types) collectProps(member, byId, accumulator, context); + return accumulator; + } + if (typeNode.type === 'reflection' && typeNode.declaration?.children) { + for (const child of typeNode.declaration.children) addProperty(child, byId, accumulator); + return accumulator; + } + if (typeNode.type === 'reference' && typeof typeNode.target === 'number') { + const target = byId.get(typeNode.target); + // Follow first-party prop types only; both declaration forms count. + if (target && (target.kind === 2_097_152 || target.kind === 256)) { + collectProps(target, byId, accumulator, context); + } + return accumulator; + } + // Partial / Omit would silently drop every prop of X. + if (typeNode.type === 'reference' && typeNode.typeArguments?.length && context) { + const firstParty = typeNode.typeArguments.find( + (argument) => argument.type === 'reference' && typeof argument.target === 'number' && byId.get(argument.target), + ); + if (firstParty) { + context.warnings.push( + `"${context.slug}": props of ${firstParty.name} are hidden behind ${typeNode.name}<...> - unwrap the utility type or extend the generator`, + ); + } + } + return accumulator; +} + +function addProperty(child, byId, accumulator) { + if (child.kind !== 1024 || accumulator.has(child.name)) return; // 1024 = Property + accumulator.set(child.name, { + name: child.name, + type: typeToString(child.type, byId), + required: !child.flags?.isOptional, + default: defaultTag(child.comment), + description: summaryText(child.comment), + }); +} + +// Merges the variants of a union/overload component into one flat table, +// noting in the description where a prop applies to some variants only. +function collectVariantProps(propsTypeNames, project, byId, warnings, slug, context) { + const perVariant = []; + for (const typeName of propsTypeNames) { + const typeNode = findTypeByName(project, typeName, warnings); + if (!typeNode) { + warnings.push(`props type "${typeName}" not found for "${slug}"`); + continue; + } + context.nativeElement ??= findNativeElement(typeNode.type, byId); + perVariant.push({ typeName, props: collectProps(typeNode, byId, new Map(), context) }); + } + + const propertyNames = new Set(); + for (const variant of perVariant) for (const name of variant.props.keys()) propertyNames.add(name); + + const merged = new Map(); + for (const propertyName of propertyNames) { + const occurrences = perVariant + .filter((variant) => variant.props.has(propertyName)) + .map((variant) => ({ typeName: variant.typeName, prop: variant.props.get(propertyName) })) + // `foo?: never` marks a prop forbidden in that variant. + .filter((occurrence) => occurrence.prop.type !== 'never'); + if (occurrences.length === 0) continue; + const distinctTypes = new Set(occurrences.map((o) => o.prop.type)); + const sharedByAll = occurrences.length === perVariant.length && distinctTypes.size === 1; + + // Required in every variant, else the table documents an impossible call. + const requiredEverywhere = + occurrences.length === perVariant.length && occurrences.every((o) => o.prop.required); + const requiredInItsVariants = !requiredEverywhere && occurrences.every((o) => o.prop.required); + + const base = occurrences[0].prop; + let description = base.description; + if (!sharedByAll) { + const variantLabel = (typeName) => typeName.replace(/Props$/, ''); + const variants = occurrences.map((o) => variantLabel(o.typeName)).join(', '); + const note = + distinctTypes.size > 1 + ? `Type varies by variant (${occurrences.map((o) => `${variantLabel(o.typeName)}: ${o.prop.type}`).join(', ')}).` + : requiredInItsVariants + ? `Only applies to the ${variants} variant (required there).` + : `Only applies to the ${variants} variant.`; + description = description ? `${description} ${note}` : note; + } + + merged.set(propertyName, { + name: propertyName, + type: sharedByAll ? base.type : [...distinctTypes].join(' | '), + required: requiredEverywhere, + default: base.default, + description, + }); + } + return merged; +} + +function extractCssVariables(directory, warnings, slug) { + // No directory - the entry documents an API, not a styled component. + if (!directory) return []; + + const abs = path.resolve(uiSource, 'components', directory); + if (!existsSync(abs)) { + warnings.push(`"${slug}": component directory ${directory} does not exist`); + return []; + } + // Subcomponents with their own page document their own variables; an + // override offered on the parent page would do nothing. + const nestedPrefixes = COMPONENTS.map((component) => component.dir) + .filter((nested) => nested?.startsWith(`${directory}/`)) + .map((nested) => `${nested.slice(directory.length + 1)}/`); + + const files = globSync('**/*.css', { cwd: abs }) + .filter((file) => !nestedPrefixes.some((prefix) => file.startsWith(prefix))) + .sort(); + const seen = new Set(); + const variables = []; + for (const file of files) { + const css = readFileSync(path.resolve(abs, file), 'utf8'); + const re = /(--ax-public-[\w-]+)\s*:\s*([^;]*?)(?:\/\*\s*(.*?)\s*\*\/)?\s*;/g; + let m; + while ((m = re.exec(css))) { + if (seen.has(m[1])) continue; + seen.add(m[1]); + const comment = (m[3] ?? '').trim(); + variables.push({ + name: m[1], + kind: valueKind(m[2].trim()), + comment: INTERNAL_NOTE_RE.test(comment) ? '' : comment, + }); + } + } + return variables; +} + +// Groups by what the value resolves to - the name misleads (`edge-stroke-width` +// is a length, `snackbar-success-border` is a color), so follow it to the literal. +const LITERAL_COLOR_RE = /^(#|rgb|hsl|oklch|color-mix|linear-gradient|radial-gradient|transparent\b|currentColor\b)/i; + +const tokenValues = readTokenValues(); + +function readTokenValues() { + const values = new Map(); + const tokenDistribution = path.resolve(repoRoot, 'packages/tokens/dist'); + if (!existsSync(tokenDistribution)) return values; + for (const file of globSync('*.css', { cwd: tokenDistribution })) { + const css = readFileSync(path.resolve(tokenDistribution, file), 'utf8'); + for (const [, name, value] of css.matchAll(/(--ax-[\w-]+)\s*:\s*([^;]+);/g)) { + if (!values.has(name)) values.set(name, value.trim()); + } + } + return values; +} + +function valueKind(value, depth = 0) { + if (LITERAL_COLOR_RE.test(value)) return 'color'; + const referenced = /var\(\s*(--[\w-]+)/.exec(value); + if (!referenced || depth > 8) return 'size'; + const resolved = tokenValues.get(referenced[1]); + return resolved ? valueKind(resolved, depth + 1) : 'size'; +} + +async function main() { + const project = await runTypedoc(); + const byId = indexById(project); + const out = {}; + const warnings = []; + + for (const component of COMPONENTS) { + let props = []; + const context = { warnings, slug: component.slug }; + if (Array.isArray(component.propsType)) { + props = [...collectVariantProps(component.propsType, project, byId, warnings, component.slug, context).values()].sort( + (a, b) => a.name.localeCompare(b.name), + ); + } else if (component.propsType) { + const typeNode = findTypeByName(project, component.propsType, warnings); + if (typeNode) { + context.nativeElement = findNativeElement(typeNode.type, byId); + props = [...collectProps(typeNode, byId, new Map(), context).values()].sort((a, b) => + a.name.localeCompare(b.name), + ); + } else { + warnings.push(`props type "${component.propsType}" not found for "${component.slug}"`); + } + } + out[component.slug] = { + name: component.name, + props, + nativeElement: context.nativeElement ?? null, + cssVariables: extractCssVariables(component.dir, warnings, component.slug), + }; + } + + await mkdir(path.dirname(outFile), { recursive: true }); + await writeFile(outFile, JSON.stringify(out, null, 2) + '\n'); + + const summary = Object.entries(out).map( + ([slug, entry]) => `${slug}: ${entry.props.length} props, ${entry.cssVariables.length} vars`, + ); + console.log('✔ ui-api.json generated\n ' + summary.join('\n ')); + + if (warnings.length > 0) { + // An unresolved type would silently ship a "no configurable props" page. + console.error('✗ ' + warnings.join('\n✗ ')); + process.exitCode = 1; + } +} + +try { + await main(); +} catch (error) { + console.error(error); + process.exitCode = 1; +} diff --git a/apps/docs/scripts/ui-components.mjs b/apps/docs/scripts/ui-components.mjs new file mode 100644 index 000000000..03f4d47a1 --- /dev/null +++ b/apps/docs/scripts/ui-components.mjs @@ -0,0 +1,68 @@ +/** + * The documented component surface: which UI components get generated Props / + * CSS variables tables, which prop type each one exposes, and where its source + * lives under packages/ui/src/components. + * + * Imported by both the generator and the coverage guard, so the guard can + * never disagree with the generator about what is documented. + */ +// `propsType`: the exported prop type, a list of variant types, or null when +// the parts are described in prose. `dir`: the folder under +// packages/ui/src/components, or null for an entry that owns no stylesheet. +export const COMPONENTS = [ + { slug: 'accordion', name: 'Accordion', propsType: 'AccordionProps', dir: 'accordion' }, + { slug: 'avatar', name: 'Avatar', propsType: 'AvatarProps', dir: 'avatar' }, + // No single props type - one of three variants depending on `children`. + { + slug: 'button', + name: 'Button', + propsType: ['LabelButtonProps', 'IconButtonProps', 'IconLabelButtonProps'], + dir: 'button', + }, + { slug: 'checkbox', name: 'Checkbox', propsType: 'CheckboxProps', dir: 'checkbox' }, + { slug: 'collapsible', name: 'Collapsible', propsType: 'CollapsibleProps', dir: 'collapsible' }, + { slug: 'date-picker', name: 'DatePicker', propsType: 'DatePickerProps', dir: 'date-picker' }, + { slug: 'icon-switch', name: 'IconSwitch', propsType: 'IconSwitchProps', dir: 'switch/icon-switch' }, + { slug: 'input', name: 'Input', propsType: 'InputProps', dir: 'input' }, + { slug: 'menu', name: 'Menu', propsType: 'MenuProps', dir: 'menu' }, + { slug: 'modal', name: 'Modal', propsType: 'ModalProps', dir: 'modal' }, + { + slug: 'nav-button', + name: 'NavButton', + propsType: ['NavLabelButtonProps', 'NavIconButtonProps', 'NavIconLabelButtonProps'], + dir: 'button/nav-button', + }, + { slug: 'radio', name: 'Radio', propsType: 'RadioProps', dir: 'radio-button' }, + { + slug: 'segment-picker', + name: 'SegmentPicker', + propsType: ['ControlledSegmentPickerProps', 'UncontrolledSegmentPickerProps'], + dir: 'segment-picker', + }, + { slug: 'select', name: 'Select', propsType: 'SelectBaseProps', dir: 'select' }, + { slug: 'separator', name: 'Separator', propsType: null, dir: 'separator' }, + { slug: 'snackbar', name: 'Snackbar', propsType: 'SnackbarProps', dir: 'snackbar' }, + { slug: 'status', name: 'Status', propsType: 'StatusProps', dir: 'status' }, + { slug: 'switch', name: 'Switch', propsType: 'BaseSwitchProps', dir: 'switch' }, + { slug: 'text-area', name: 'TextArea', propsType: 'TextAreaProps', dir: 'text-area' }, + { slug: 'tooltip', name: 'Tooltip', propsType: 'TooltipProps', dir: 'tooltip' }, + // Diagram components. + { slug: 'node-icon', name: 'NodeIcon', propsType: 'NodeIconProps', dir: 'node/node-icon' }, + { + slug: 'node-description', + name: 'NodeDescription', + propsType: 'NodeDescriptionProps', + dir: 'node/node-description', + }, + { + slug: 'node-as-port-wrapper', + name: 'NodeAsPortWrapper', + propsType: 'NodeAsPortWrapperProps', + dir: 'node/node-as-port-wrapper', + }, + { slug: 'edge', name: 'EdgeLabel', propsType: 'EdgeLabelProps', dir: 'edge' }, + // Documented on the Edge page, which already renders the edge variables. + { slug: 'use-edge-style', name: 'useEdgeStyle', propsType: 'UseEdgeStyleParams', dir: null }, + // Compound component - parts described in prose, variables generated. + { slug: 'node-panel', name: 'NodePanel', propsType: null, dir: 'node/node-panel' }, +]; diff --git a/apps/docs/src/components/api/css-variables-table.astro b/apps/docs/src/components/api/css-variables-table.astro new file mode 100644 index 000000000..01c8cde06 --- /dev/null +++ b/apps/docs/src/components/api/css-variables-table.astro @@ -0,0 +1,79 @@ +--- +// CSS custom properties of a UI component, generated from source into ui-api.json. +import data from '../../generated/ui-api.json'; + +interface CssVariable { + name: string; + kind: 'color' | 'size'; + comment: string; +} + +const { slug } = Astro.props as { slug: string }; +const entry = (data as Record)[slug]; +if (!entry) { + throw new Error( + `ui-api.json has no entry for slug "${slug}" - check the slug against COMPONENTS in generate-ui-api.mjs`, + ); +} +const cssVariables = entry?.cssVariables ?? []; + +const groups = [ + { label: 'Color', vars: cssVariables.filter((v) => v.kind === 'color') }, + { label: 'Size', vars: cssVariables.filter((v) => v.kind !== 'color') }, +].filter((group) => group.vars.length > 0); +--- + +{ + cssVariables.length === 0 ? ( +

    This component does not expose its own CSS variables.

    + ) : ( +
    + {groups.map((group) => ( +
    +

    {group.label}

    + {group.vars.map((cssVariable) => ( +
    + {cssVariable.name} + {cssVariable.comment && {cssVariable.comment}} +
    + ))} +
    + ))} +
    + ) +} + + diff --git a/apps/docs/src/components/api/props-table.astro b/apps/docs/src/components/api/props-table.astro new file mode 100644 index 000000000..0f6d6b3c1 --- /dev/null +++ b/apps/docs/src/components/api/props-table.astro @@ -0,0 +1,128 @@ +--- +// Props of a UI component, generated from source into ui-api.json. +import data from '../../generated/ui-api.json'; + +interface PropertyRow { + name: string; + type: string; + required: boolean; + default: string | null; + description: string; +} + +interface Entry { + props: PropertyRow[]; + nativeElement: string | null; +} + +const { slug } = Astro.props as { slug: string }; +const entry = (data as Record)[slug]; +if (!entry) { + throw new Error( + `ui-api.json has no entry for slug "${slug}" - check the slug against COMPONENTS in generate-ui-api.mjs`, + ); +} +const props = [...(entry?.props ?? [])].sort( + (a, b) => Number(b.required) - Number(a.required) || a.name.localeCompare(b.name), +); +const nativeElement = entry?.nativeElement ?? null; +--- + +{ + nativeElement && ( +

    + Also accepts every standard <{nativeElement}> attribute (onChange,{' '} + placeholder, aria-*, …); they are forwarded to the underlying element. +

    + ) +} + +{ + props.length === 0 ? ( +

    This component has no configurable props.

    + ) : ( +
    + {props.map((property) => ( +
    +
    + {property.name} + {property.required && required} +
    +
    + Type + {property.type} +
    + {property.default && ( +
    + Default + {property.default} +
    + )} + {property.description &&

    {property.description.replaceAll('\n', ' ')}

    } +
    + ))} +
    + ) +} + + diff --git a/apps/docs/src/components/ui-examples/accordion.tsx b/apps/docs/src/components/ui-examples/accordion.tsx new file mode 100644 index 000000000..f873f4260 --- /dev/null +++ b/apps/docs/src/components/ui-examples/accordion.tsx @@ -0,0 +1,13 @@ +import { Accordion } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function AccordionExample() { + return ( + + + Our component library, built on Base UI. + + + ); +} diff --git a/apps/docs/src/components/ui-examples/avatar.tsx b/apps/docs/src/components/ui-examples/avatar.tsx new file mode 100644 index 000000000..02cd08366 --- /dev/null +++ b/apps/docs/src/components/ui-examples/avatar.tsx @@ -0,0 +1,17 @@ +import { Avatar } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +const AVATAR = + 'data:image/svg+xml;utf8,' + + encodeURIComponent( + "AL", + ); + +export function AvatarExample() { + return ( + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/button.tsx b/apps/docs/src/components/ui-examples/button.tsx new file mode 100644 index 000000000..c2e5a32e9 --- /dev/null +++ b/apps/docs/src/components/ui-examples/button.tsx @@ -0,0 +1,11 @@ +import { Button } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function ButtonExample() { + return ( + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/checkbox.tsx b/apps/docs/src/components/ui-examples/checkbox.tsx new file mode 100644 index 000000000..5c7176382 --- /dev/null +++ b/apps/docs/src/components/ui-examples/checkbox.tsx @@ -0,0 +1,14 @@ +import { Checkbox } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function CheckboxExample() { + const [checked, setChecked] = useState(true); + + return ( + + setChecked(event.target.checked)} /> + + ); +} diff --git a/apps/docs/src/components/ui-examples/collapsible.tsx b/apps/docs/src/components/ui-examples/collapsible.tsx new file mode 100644 index 000000000..3abaf5645 --- /dev/null +++ b/apps/docs/src/components/ui-examples/collapsible.tsx @@ -0,0 +1,14 @@ +import { Collapsible } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function CollapsibleExample() { + return ( + + + + Additional details go here. + + + ); +} diff --git a/apps/docs/src/components/ui-examples/component-preview.module.css b/apps/docs/src/components/ui-examples/component-preview.module.css new file mode 100644 index 000000000..be646fae6 --- /dev/null +++ b/apps/docs/src/components/ui-examples/component-preview.module.css @@ -0,0 +1,42 @@ +.stage { + position: relative; + width: 100%; + max-width: 50rem; + aspect-ratio: 2 / 1; + /* The ratio is a preferred size, not a clamp: overflow:hidden zeroes the + content-based automatic minimum, so restore it - oversized examples grow + the stage instead of being clipped. */ + min-height: fit-content; + margin-top: 1rem; + padding: 1rem; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--sl-color-gray-5); + border-radius: 0.5rem; + background-color: var(--sl-color-bg); + /* dot grid */ + background-image: radial-gradient(circle at center, var(--sl-color-gray-5) 1.5px, transparent 1.5px); + background-size: 1.5rem 1.5rem; +} + +/* A soft radial of the page background behind the component so it reads cleanly + over the dot grid (mirrors the reference preview spotlight). */ +.spotlight { + display: flex; + align-items: center; + justify-content: center; + padding: 6rem 10rem; + margin: -6rem -10rem; + background: radial-gradient(closest-side, var(--sl-color-bg) 45%, transparent 100%); + max-width: calc(100% + 20rem); +} + +.host { + display: flex; + align-items: center; + justify-content: center; + max-width: 100%; + min-width: 0; +} diff --git a/apps/docs/src/components/ui-examples/component-preview.tsx b/apps/docs/src/components/ui-examples/component-preview.tsx new file mode 100644 index 000000000..5ca9ad289 --- /dev/null +++ b/apps/docs/src/components/ui-examples/component-preview.tsx @@ -0,0 +1,38 @@ +import componentCss from '@workflowbuilder/ui/index.css?raw'; +import globalCss from '@workflowbuilder/ui/styles.css?raw'; +import { type ReactNode, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import styles from './component-preview.module.css'; + +// Examples render in a shadow root so Starlight's rules cannot reach them and +// the library's cannot leak out. Inherited and custom properties still cross +// the boundary - that is how the docs theme reaches the examples. Inside a +// shadow root `:root` matches nothing, hence the retarget to `:host`. +const shadowCss = `${`${globalCss}\n${componentCss}`.replaceAll(':root', ':host')} +:host > :not(style) { max-width: 100%; }`; + +export function ComponentPreview({ children }: { children: ReactNode }) { + const hostRef = useRef(null); + const [shadow, setShadow] = useState(null); + + useEffect(() => { + const host = hostRef.current; + if (!host || shadow) return; + const root = host.attachShadow({ mode: 'open' }); + const style = document.createElement('style'); + style.textContent = shadowCss; + root.append(style); + setShadow(root); + }, [shadow]); + + return ( +
    +
    +
    + {shadow ? createPortal(children, shadow) : null} +
    +
    +
    + ); +} diff --git a/apps/docs/src/components/ui-examples/date-picker.tsx b/apps/docs/src/components/ui-examples/date-picker.tsx new file mode 100644 index 000000000..bc5575afd --- /dev/null +++ b/apps/docs/src/components/ui-examples/date-picker.tsx @@ -0,0 +1,19 @@ +import { DatePicker } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function DatePickerExample() { + const [date, setDate] = useState(null); + + return ( + + setDate((next as Date | null) ?? null)} + /> + + ); +} diff --git a/apps/docs/src/components/ui-examples/edge.tsx b/apps/docs/src/components/ui-examples/edge.tsx new file mode 100644 index 000000000..cec6f5def --- /dev/null +++ b/apps/docs/src/components/ui-examples/edge.tsx @@ -0,0 +1,31 @@ +import { DiamondsFour } from '@phosphor-icons/react'; +import { EdgeLabel } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +// EdgeLabel defaults to `position: absolute` for placement on a diagram edge. +// Outside a canvas we override it to `relative` so the variants lay out inline. +export function EdgeExample() { + return ( + +
    + Edge Label + + + Edge Label + + + + Edge Label + + + + Edge Label + + + + +
    +
    + ); +} diff --git a/apps/docs/src/components/ui-examples/input.tsx b/apps/docs/src/components/ui-examples/input.tsx new file mode 100644 index 000000000..f1d4cc4b2 --- /dev/null +++ b/apps/docs/src/components/ui-examples/input.tsx @@ -0,0 +1,14 @@ +import { Input } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function InputExample() { + const [value, setValue] = useState(''); + + return ( + + setValue(event.target.value)} /> + + ); +} diff --git a/apps/docs/src/components/ui-examples/menu.tsx b/apps/docs/src/components/ui-examples/menu.tsx new file mode 100644 index 000000000..8583d4787 --- /dev/null +++ b/apps/docs/src/components/ui-examples/menu.tsx @@ -0,0 +1,20 @@ +import { Button, Menu } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function MenuExample() { + return ( + + {} }, + { label: 'Duplicate', onClick: () => {} }, + { type: 'separator' }, + { label: 'Delete', destructive: true, onClick: () => {} }, + ]} + > + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/modal.tsx b/apps/docs/src/components/ui-examples/modal.tsx new file mode 100644 index 000000000..07b4de026 --- /dev/null +++ b/apps/docs/src/components/ui-examples/modal.tsx @@ -0,0 +1,29 @@ +import { Button, Modal } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function ModalExample() { + const [open, setOpen] = useState(false); + + return ( + + + setOpen(false)} + title="Example modal" + subtitle="Rendered live from @workflowbuilder/ui" + footer={ + + } + > + The backdrop and popup fade in and out via the Base UI transition lifecycle. + + + ); +} diff --git a/apps/docs/src/components/ui-examples/nav-button.tsx b/apps/docs/src/components/ui-examples/nav-button.tsx new file mode 100644 index 000000000..edba42241 --- /dev/null +++ b/apps/docs/src/components/ui-examples/nav-button.tsx @@ -0,0 +1,11 @@ +import { NavButton } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function NavButtonExample() { + return ( + + Nav button + + ); +} diff --git a/apps/docs/src/components/ui-examples/node-description.tsx b/apps/docs/src/components/ui-examples/node-description.tsx new file mode 100644 index 000000000..bb552affb --- /dev/null +++ b/apps/docs/src/components/ui-examples/node-description.tsx @@ -0,0 +1,15 @@ +import { NodeDescription, NodePanel } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function NodeDescriptionExample() { + return ( + + + + + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/node-icon.tsx b/apps/docs/src/components/ui-examples/node-icon.tsx new file mode 100644 index 000000000..b527c3349 --- /dev/null +++ b/apps/docs/src/components/ui-examples/node-icon.tsx @@ -0,0 +1,17 @@ +import { User } from '@phosphor-icons/react'; +import { NodeIcon, NodePanel } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function NodeIconExample() { + return ( + + + + } /> + Node with Icon + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/node-panel.tsx b/apps/docs/src/components/ui-examples/node-panel.tsx new file mode 100644 index 000000000..ff561ca49 --- /dev/null +++ b/apps/docs/src/components/ui-examples/node-panel.tsx @@ -0,0 +1,17 @@ +import { User } from '@phosphor-icons/react'; +import { NodeDescription, NodeIcon, NodePanel } from '@workflowbuilder/ui'; + +import { ComponentPreview } from './component-preview'; + +export function NodePanelExample() { + return ( + + + + } /> + + + + + ); +} diff --git a/apps/docs/src/components/ui-examples/radio.tsx b/apps/docs/src/components/ui-examples/radio.tsx new file mode 100644 index 000000000..e50209f0e --- /dev/null +++ b/apps/docs/src/components/ui-examples/radio.tsx @@ -0,0 +1,18 @@ +import { Radio } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function RadioExample() { + const [value, setValue] = useState('daily'); + + return ( + + + {['daily', 'weekly', 'monthly'].map((v) => ( + setValue(v)} /> + ))} + + + ); +} diff --git a/apps/docs/src/components/ui-examples/segment-picker.tsx b/apps/docs/src/components/ui-examples/segment-picker.tsx new file mode 100644 index 000000000..023fcc8ff --- /dev/null +++ b/apps/docs/src/components/ui-examples/segment-picker.tsx @@ -0,0 +1,18 @@ +import { SegmentPicker } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +export function SegmentPickerExample() { + const [view, setView] = useState('list'); + + return ( + + setView(next)}> + List + Grid + Board + + + ); +} diff --git a/apps/docs/src/components/ui-examples/select.tsx b/apps/docs/src/components/ui-examples/select.tsx new file mode 100644 index 000000000..9b9831a76 --- /dev/null +++ b/apps/docs/src/components/ui-examples/select.tsx @@ -0,0 +1,20 @@ +import { Select, type SelectItem } from '@workflowbuilder/ui'; +import { useState } from 'react'; + +import { ComponentPreview } from './component-preview'; + +const ITEMS: SelectItem[] = [ + { value: 'opus', label: 'Claude Opus' }, + { value: 'sonnet', label: 'Claude Sonnet' }, + { value: 'haiku', label: 'Claude Haiku' }, +]; + +export function SelectExample() { + const [model, setModel] = useState('opus'); + + return ( + +