From 434f4132cf2475f7fa1b06a3c045b0f6fad4c94a Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Wed, 24 Jun 2026 14:12:37 +0200 Subject: [PATCH 01/42] feat(ui): add @workflowbuilder/ui and @workflowbuilder/ui-tokens packages --- CLAUDE.md | 54 +- apps/docs/src/generated/ui-api.json | 2294 +++++++++++ knip.config.js | 11 + package.json | 3 +- packages/tokens/config.ts | 15 + packages/tokens/output.json | 0 packages/tokens/package.json | 19 + packages/tokens/src/constants.ts | 2 + packages/tokens/src/eject-tokens.ts | 29 + packages/tokens/src/generate-css-bundle.ts | 31 + packages/tokens/src/index.ts | 8 + packages/tokens/src/to-file-name.ts | 8 + packages/tokens/src/tokens-to-css.ts | 107 + packages/tokens/src/types.ts | 9 + packages/tokens/tokens.json | 3408 +++++++++++++++++ packages/tokens/tsconfig.json | 3 + packages/ui/CHANGELOG.md | 62 + packages/ui/README.md | 150 + packages/ui/combine-css-bundle.mts | 54 + packages/ui/css-layers.md | 32 + packages/ui/eslint.config.mjs | 44 + packages/ui/package.json | 94 + packages/ui/postcss-box-sizing.mts | 42 + packages/ui/scripts/check-built-css.ts | 59 + .../components/accordion/accordion.module.css | 23 + .../ui/src/components/accordion/accordion.tsx | 78 + 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 + 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 | 104 + .../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 | 37 + .../components/date-picker/date-picker.tsx | 350 ++ .../ui/src/components/date-picker/index.ts | 2 + .../ui/src/components/date-picker/types.ts | 80 + .../src/components/date-picker/variables.css | 11 + .../edge-label/edge-label-size.module.css | 99 + .../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 | 37 + 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 | 127 + packages/ui/src/components/menu/types.ts | 8 + packages/ui/src/components/modal/index.ts | 2 + .../ui/src/components/modal/modal.module.css | 216 ++ packages/ui/src/components/modal/modal.tsx | 111 + 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 | 8 + .../node-description.module.css | 25 + .../node-description/node-description.tsx | 18 + .../node/node-icon/node-icon.module.css | 21 + .../components/node/node-icon/node-icon.tsx | 13 + .../node/node-panel/handle.module.css | 96 + .../node/node-panel/node-panel.module.css | 74 + .../components/node/node-panel/node-panel.tsx | 137 + .../ui/src/components/radio-button/index.ts | 1 + .../radio-button/radio-size.module.css | 39 + .../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 + .../segment-picker/utils/get-valid-shape.ts | 22 + packages/ui/src/components/select/index.ts | 2 + .../select-button/select-button.module.css | 56 + .../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 | 6 + .../snackbar/components/action-buttons.tsx | 43 + .../snackbar/components/icon.module.css | 31 + .../components/snackbar/components/icon.tsx | 24 + .../snackbar/components/message.module.css | 16 + .../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 | 62 + 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 | 92 + .../switch/icon-switch/icon-switch.tsx | 47 + 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 | 87 + packages/ui/src/components/text-area/index.ts | 1 + .../components/text-area/text-area.module.css | 62 + .../ui/src/components/text-area/text-area.tsx | 116 + packages/ui/src/components/tooltip/index.ts | 4 + .../components/tooltip/tooltip-content.tsx | 48 + .../components/tooltip/tooltip-trigger.tsx | 52 + .../src/components/tooltip/tooltip.module.css | 35 + .../ui/src/components/tooltip/tooltip.tsx | 115 + packages/ui/src/components/tooltip/types.ts | 1 + packages/ui/src/index.ts | 30 + .../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 | 68 + 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.ts | 6 + packages/ui/src/styles/globals.css | 3 + packages/ui/src/styles/layers.css | 1 + packages/ui/src/styles/typography.css | 167 + packages/ui/tsconfig.json | 12 + packages/ui/types.d.ts | 1 + packages/ui/vite.config.mts | 126 + pnpm-lock.yaml | 1954 +++++----- pnpm-workspace.yaml | 5 + 178 files changed, 13608 insertions(+), 889 deletions(-) create mode 100644 apps/docs/src/generated/ui-api.json create mode 100644 packages/tokens/config.ts create mode 100644 packages/tokens/output.json 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/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/ui/CHANGELOG.md 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/package.json create mode 100644 packages/ui/postcss-box-sizing.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.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/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/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.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/tooltip-content.tsx 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.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/types.d.ts create mode 100644 packages/ui/vite.config.mts diff --git a/CLAUDE.md b/CLAUDE.md index b7e76c160..96550df58 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,22 @@ 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/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 @@ -133,7 +137,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 (`@workflowbuilder/ui-tokens` is private too) and listed under `ignore` in `.changeset/config.json`. Because there are now two publishable packages, the release tag scheme below (single-package `v*`) still needs migrating to scoped tags (`@workflowbuilder/sdk@X.Y.Z`) before publishing `@workflowbuilder/ui` from this repo - see § "Tag format" and `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 +162,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". +Tag format currently follows the ng-diagram convention (single-package monorepo, `v*` regex). A second publishable package (`@workflowbuilder/ui`) now exists, so this needs migrating to scoped tags (`@workflowbuilder/sdk@X.Y.Z`) — ~1h of work, 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/apps/docs/src/generated/ui-api.json b/apps/docs/src/generated/ui-api.json new file mode 100644 index 000000000..9c96ce391 --- /dev/null +++ b/apps/docs/src/generated/ui-api.json @@ -0,0 +1,2294 @@ +{ + "accordion": { + "name": "Accordion", + "props": [ + { + "name": "children", + "type": "React.ReactNode", + "required": true, + "default": null, + "description": "Contents of the collapsible section" + }, + { + "name": "defaultOpen", + "type": "boolean", + "required": false, + "default": "true", + "description": "Initial open state" + }, + { + "name": "isOpen", + "type": "boolean", + "required": false, + "default": null, + "description": "True if not collapsed" + }, + { + "name": "label", + "type": "string", + "required": true, + "default": null, + "description": "Text displayed in the header" + }, + { + "name": "onToggleOpen", + "type": "(isOpen: boolean) => void", + "required": false, + "default": null, + "description": "Callback run when the open state changes" + } + ], + "cssVariables": [] + }, + "avatar": { + "name": "Avatar", + "props": [ + { + "name": "imageUrl", + "type": "string", + "required": false, + "default": null, + "description": "Image URL" + }, + { + "name": "size", + "type": "Size", + "required": false, + "default": "'extra-large'", + "description": "Size of the circle container" + }, + { + "name": "username", + "type": "string", + "required": true, + "default": null, + "description": "Provide to use it as alt of the image for better a11y" + } + ], + "cssVariables": [ + { + "name": "--ax-public-avatar-border-color", + "comment": "" + }, + { + "name": "--ax-public-avatar-background-color", + "comment": "" + }, + { + "name": "--ax-public-avatar-border-size", + "comment": "" + } + ] + }, + "button": { + "name": "Button", + "props": [ + { + "name": "size", + "type": "ButtonSize", + "required": false, + "default": null, + "description": "" + }, + { + "name": "tooltip", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "tooltipType", + "type": "TooltipVariant", + "required": false, + "default": null, + "description": "" + }, + { + "name": "variant", + "type": "Variant", + "required": false, + "default": null, + "description": "" + } + ], + "cssVariables": [ + { + "name": "--ax-public-button-background-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-focus-visible-box-shadow-color", + "comment": "" + }, + { + "name": "--ax-public-button-inner-border-color", + "comment": "" + }, + { + "name": "--ax-public-button-color", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-hover", + "comment": "" + }, + { + "name": "--ax-public-button-gap-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-gap-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-size-xx-small", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-xxx-small", + "comment": "missing token" + }, + { + "name": "--ax-public-button-nav-background-color", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-active", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-loader-dot-size", + "comment": "" + }, + { + "name": "--ax-public-button-loader-dot-border-radius", + "comment": "" + }, + { + "name": "--ax-public-button-loader-dot-color", + "comment": "" + }, + { + "name": "--ax-public-button-loader-animation-duration", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-extra-large", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-large", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-medium", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-extra-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-circle", + "comment": "" + }, + { + "name": "--ax-public-button-gap-extra-large", + "comment": "" + }, + { + "name": "--ax-public-button-gap-large", + "comment": "" + }, + { + "name": "--ax-public-button-gap-medium", + "comment": "" + }, + { + "name": "--ax-public-button-gap-small", + "comment": "" + }, + { + "name": "--ax-public-button-gap-extra-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-extra-large", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-large", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-medium", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-extra-small", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-extra-large", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-large", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-medium", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-small", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-extra-small", + "comment": "" + }, + { + "name": "--ax-public-icon-size-extra-large", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-large", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-medium", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-small", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-extra-small", + "comment": "missing token" + }, + { + "name": "--ax-public-label-button-padding-extra-large", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-large", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-medium", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-small", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-extra-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-size", + "comment": "" + }, + { + "name": "--ax-public-button-primary-background", + "comment": "" + }, + { + "name": "--ax-public-button-primary-background-hover", + "comment": "" + }, + { + "name": "--ax-public-button-primary-background-active", + "comment": "" + }, + { + "name": "--ax-public-button-primary-background-focus", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-border-color", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-color", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-border-color-hover", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-color-active", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-border-color-active", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-border-color-focus", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-border-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-secondary-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-background-color", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-border-color", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-color", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-background-color-hover", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-background-color-active", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-background-color-focus", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-background-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-ghost-destructive-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-button-gray-background", + "comment": "" + }, + { + "name": "--ax-public-button-gray-background-hover", + "comment": "" + }, + { + "name": "--ax-public-button-gray-background-active", + "comment": "" + }, + { + "name": "--ax-public-button-gray-background-focus", + "comment": "" + }, + { + "name": "--ax-public-button-error-background", + "comment": "" + }, + { + "name": "--ax-public-button-error-background-hover", + "comment": "" + }, + { + "name": "--ax-public-button-error-background-active", + "comment": "" + }, + { + "name": "--ax-public-button-error-background-focus", + "comment": "" + }, + { + "name": "--ax-public-button-success-background", + "comment": "" + }, + { + "name": "--ax-public-button-success-background-hover", + "comment": "" + }, + { + "name": "--ax-public-button-success-background-active", + "comment": "" + }, + { + "name": "--ax-public-button-success-background-focus", + "comment": "" + }, + { + "name": "--ax-public-button-warning-background", + "comment": "" + }, + { + "name": "--ax-public-button-warning-background-hover", + "comment": "" + }, + { + "name": "--ax-public-button-warning-background-active", + "comment": "" + }, + { + "name": "--ax-public-button-warning-background-focus", + "comment": "" + } + ] + }, + "checkbox": { + "name": "Checkbox", + "props": [ + { + "name": "checked", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the checkbox is checked" + }, + { + "name": "indeterminate", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the checkbox is in an indeterminate state" + }, + { + "name": "size", + "type": "SelectorSize", + "required": false, + "default": "'medium'", + "description": "The size of the checkbox" + } + ], + "cssVariables": [ + { + "name": "--ax-public-checkbox-size-medium", + "comment": "" + }, + { + "name": "--ax-public-checkbox-size-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-size-extra-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-icon-size-medium", + "comment": "" + }, + { + "name": "--ax-public-checkbox-icon-size-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-icon-size-extra-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-border-radius-medium", + "comment": "" + }, + { + "name": "--ax-public-checkbox-border-radius-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-border-radius-extra-small", + "comment": "" + }, + { + "name": "--ax-public-checkbox-border-width", + "comment": "" + }, + { + "name": "--ax-public-checkbox-border-color", + "comment": "" + }, + { + "name": "--ax-public-checkbox-bg", + "comment": "" + }, + { + "name": "--ax-public-checkbox-icon-color", + "comment": "" + }, + { + "name": "--ax-public-checkbox-checked-bg", + "comment": "" + }, + { + "name": "--ax-public-checkbox-focus-border-color", + "comment": "" + }, + { + "name": "--ax-public-checkbox-focus-border-shadow", + "comment": "" + }, + { + "name": "--ax-public-checkbox-disabled-bg", + "comment": "" + }, + { + "name": "--ax-public-checkbox-disabled-border-color", + "comment": "" + }, + { + "name": "--ax-public-checkbox-disabled-icon-color", + "comment": "" + } + ] + }, + "date-picker": { + "name": "DatePicker", + "props": [ + { + "name": "aria-label", + "type": "string", + "required": false, + "default": null, + "description": "Accessible name for the trigger button." + }, + { + "name": "aria-labelledby", + "type": "string", + "required": false, + "default": null, + "description": "`aria-labelledby` for the trigger button." + }, + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "Class name applied to the trigger button." + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": null, + "description": "Disable the picker. The trigger button is not interactive and the\npopover cannot be opened." + }, + { + "name": "id", + "type": "string", + "required": false, + "default": null, + "description": "`id` attribute applied to the trigger button." + }, + { + "name": "inputSize", + "type": "ItemSize", + "required": false, + "default": "'medium'", + "description": "Size variant of the trigger input." + }, + { + "name": "maxDate", + "type": "Date", + "required": false, + "default": null, + "description": "The latest selectable date (inclusive)." + }, + { + "name": "minDate", + "type": "Date", + "required": false, + "default": null, + "description": "The earliest selectable date (inclusive)." + }, + { + "name": "onChange", + "type": "(value: Date | [Date, Date] | Date[] | null) => void", + "required": false, + "default": null, + "description": "Callback fired when the selected value changes.\n\n- `default` -> the selected `Date` or `null` when cleared\n- `range` -> `[from, to]` once both dates are selected, otherwise `null`\n- `multiple`-> the array of selected `Date`s (empty array allowed)" + }, + { + "name": "readOnly", + "type": "boolean", + "required": false, + "default": null, + "description": "Render the picker as read-only. The trigger displays the current value\nbut the popover cannot be opened." + } + ], + "cssVariables": [ + { + "name": "--ax-public-date-picker-dropdown-background", + "comment": "" + }, + { + "name": "--ax-public-date-picker-dropdown-border-color", + "comment": "" + }, + { + "name": "--ax-public-date-picker-dropdown-box-shadow", + "comment": "" + }, + { + "name": "--ax-public-date-picker-color", + "comment": "" + }, + { + "name": "--ax-public-date-picker-header-background", + "comment": "" + }, + { + "name": "--ax-public-date-picker-date-header-color", + "comment": "" + }, + { + "name": "--ax-public-date-picker-date-outside-color", + "comment": "" + }, + { + "name": "--ax-public-date-picker-date-selected-background-color", + "comment": "" + } + ] + }, + "input": { + "name": "Input", + "props": [ + { + "name": "endAdornment", + "type": "ReactNode", + "required": false, + "default": null, + "description": "Element displayed at the end of the input field." + }, + { + "name": "error", + "type": "boolean", + "required": false, + "default": "false", + "description": "Renders the input in an error state." + }, + { + "name": "size", + "type": "ItemSize", + "required": false, + "default": "'medium'", + "description": "Specifies the size of the input field.\nCan be 'small', 'medium', or 'large'." + }, + { + "name": "startAdornment", + "type": "ReactNode", + "required": false, + "default": null, + "description": "Element displayed at the start of the input field." + } + ], + "cssVariables": [ + { + "name": "--ax-public-input-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-input-root-color", + "comment": "" + }, + { + "name": "--ax-public-input-root-border-color", + "comment": "" + }, + { + "name": "--ax-public-input-root-border-color-focus", + "comment": "" + }, + { + "name": "--ax-public-input-root-border-size", + "comment": "" + }, + { + "name": "--ax-public-input-root-background-color-error", + "comment": "" + }, + { + "name": "--ax-public-input-root-border-color-error", + "comment": "" + }, + { + "name": "--ax-public-input-root-color-error", + "comment": "" + }, + { + "name": "--ax-public-input-root-color-disabled", + "comment": "" + } + ] + }, + "menu": { + "name": "Menu", + "props": [ + { + "name": "children", + "type": "ReactElement", + "required": false, + "default": null, + "description": "The trigger element that will open the menu when clicked.\nThis element will be wrapped in a button with appropriate ARIA attributes." + }, + { + "name": "items", + "type": "MenuItemProps[]", + "required": true, + "default": null, + "description": "Array of menu items to be rendered in the menu.\nEach item can be either a regular menu item or a separator." + }, + { + "name": "offset", + "type": "OffsetOptions", + "required": false, + "default": null, + "description": "Distance between a popup and the trigger element" + }, + { + "name": "onOpenChange", + "type": "(open: boolean, event: Event) => void", + "required": false, + "default": null, + "description": "Callback fired when the component requests to be opened or closed.\nReceives the next open state and the native event that triggered the\nchange (if any)." + }, + { + "name": "open", + "type": "boolean", + "required": false, + "default": null, + "description": "Controls whether the menu is open or closed.\nWhen omitted, the menu's open state will be managed internally\nand toggled by clicking on the `children` trigger element." + }, + { + "name": "placement", + "type": "Placement", + "required": false, + "default": "'bottom-end'", + "description": "The preferred placement of the menu relative to its trigger element.\nUses Floating UI placement options." + }, + { + "name": "size", + "type": "ItemSize", + "required": false, + "default": "'medium'", + "description": "Size variant for the menu items." + } + ], + "cssVariables": [] + }, + "modal": { + "name": "Modal", + "props": [ + { + "name": "children", + "type": "ReactNode", + "required": false, + "default": null, + "description": "Content to be displayed in the modal body" + }, + { + "name": "footer", + "type": "ReactNode", + "required": false, + "default": null, + "description": "Content to be displayed in the modal footer" + }, + { + "name": "footerVariant", + "type": "FooterVariant", + "required": false, + "default": "'integrated'", + "description": "Variant of the footer styling" + }, + { + "name": "onClose", + "type": "() => void", + "required": false, + "default": null, + "description": "Callback function called when the modal is closed" + }, + { + "name": "open", + "type": "boolean", + "required": true, + "default": null, + "description": "Controls the visibility of the modal" + }, + { + "name": "size", + "type": "'regular' | 'large'", + "required": false, + "default": "'regular'", + "description": "Size variant of the modal" + }, + { + "name": "subtitle", + "type": "string", + "required": false, + "default": null, + "description": "Optional subtitle displayed below the title" + }, + { + "name": "title", + "type": "string", + "required": true, + "default": null, + "description": "Title displayed in the modal header" + } + ], + "cssVariables": [ + { + "name": "--ax-public-modal-width", + "comment": "" + }, + { + "name": "--ax-public-modal-border-radius", + "comment": "" + }, + { + "name": "--ax-public-modal-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-description-size", + "comment": "" + }, + { + "name": "--ax-public-modal-description-line-height", + "comment": "" + }, + { + "name": "--ax-public-modal-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-content-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-footer-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-background", + "comment": "" + }, + { + "name": "--ax-public-modal-backdrop-background", + "comment": "" + }, + { + "name": "--ax-public-modal-close-button-color", + "comment": "" + }, + { + "name": "--ax-public-modal-header-background", + "comment": "" + }, + { + "name": "--ax-public-modal-header-border-color", + "comment": "" + }, + { + "name": "--ax-public-modal-title-color", + "comment": "" + }, + { + "name": "--ax-public-modal-description-color", + "comment": "" + }, + { + "name": "--ax-public-modal-icon-background", + "comment": "" + }, + { + "name": "--ax-public-modal-icon-color", + "comment": "" + }, + { + "name": "--ax-public-modal-content-background", + "comment": "" + }, + { + "name": "--ax-public-modal-footer-border-color", + "comment": "" + }, + { + "name": "--ax-public-modal-large-width", + "comment": "" + }, + { + "name": "--ax-public-modal-large-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-large-title-size", + "comment": "" + }, + { + "name": "--ax-public-modal-large-title-line-height", + "comment": "" + }, + { + "name": "--ax-public-modal-large-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-modal-box-shadow", + "comment": "" + }, + { + "name": "--ax-public-modal-gap", + "comment": "" + }, + { + "name": "--ax-public-modal-gap-large", + "comment": "" + }, + { + "name": "--ax-public-modal-icon-border-radius", + "comment": "" + }, + { + "name": "--ax-public-modal-icon-border-radius-large", + "comment": "" + }, + { + "name": "--ax-public-modal-content-gap", + "comment": "" + } + ] + }, + "nav-button": { + "name": "NavButton", + "props": [ + { + "name": "isSelected", + "type": "boolean", + "required": false, + "default": null, + "description": "" + }, + { + "name": "size", + "type": "Size", + "required": false, + "default": "'medium'", + "description": "Size variant of the nav button." + }, + { + "name": "tooltip", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "tooltipType", + "type": "TooltipVariant", + "required": false, + "default": null, + "description": "" + } + ], + "cssVariables": [ + { + "name": "--ax-public-button-icon-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-icon-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-hover", + "comment": "" + }, + { + "name": "--ax-public-button-gap-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-gap-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-label-button-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-xx-small", + "comment": "" + }, + { + "name": "--ax-public-label-button-padding-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-xx-small", + "comment": "" + }, + { + "name": "--ax-public-button-border-radius-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-icon-size-xx-small", + "comment": "missing token" + }, + { + "name": "--ax-public-icon-size-xxx-small", + "comment": "missing token" + }, + { + "name": "--ax-public-button-nav-background-color", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-active", + "comment": "" + }, + { + "name": "--ax-public-button-nav-color-disabled", + "comment": "" + } + ] + }, + "radio": { + "name": "Radio", + "props": [ + { + "name": "checked", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the radio button is checked" + }, + { + "name": "name", + "type": "string", + "required": true, + "default": null, + "description": "The name of the radio button group" + }, + { + "name": "onChange", + "type": "(event: React.ChangeEvent) => void", + "required": false, + "default": null, + "description": "Callback fired when the radio button state changes" + }, + { + "name": "size", + "type": "SelectorSize", + "required": false, + "default": "'medium'", + "description": "The size of the radio button" + }, + { + "name": "value", + "type": "string | number", + "required": true, + "default": null, + "description": "The value of the radio button" + } + ], + "cssVariables": [ + { + "name": "--ax-public-radio-size-medium", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-size-small", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-size-extra-small", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-dot-size-medium", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-dot-size-small", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-dot-size-extra-small", + "comment": "missing token" + }, + { + "name": "--ax-public-radio-border-color", + "comment": "" + }, + { + "name": "--ax-public-radio-bg", + "comment": "" + }, + { + "name": "--ax-public-radio-checked-bg", + "comment": "" + }, + { + "name": "--ax-public-radio-dot-color", + "comment": "" + }, + { + "name": "--ax-public-radio-focus-border-color", + "comment": "" + }, + { + "name": "--ax-public-radio-disabled-border-color", + "comment": "" + }, + { + "name": "--ax-public-radio-disabled-bg", + "comment": "" + }, + { + "name": "--ax-public-radio-disabled-dot-color", + "comment": "" + }, + { + "name": "--ax-public-radio-border-radius", + "comment": "" + } + ] + }, + "segment-picker": { + "name": "SegmentPicker", + "props": [ + { + "name": "children", + "type": "ReactElement[]", + "required": true, + "default": null, + "description": "" + }, + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "defaultValue", + "type": "never", + "required": false, + "default": null, + "description": "Must not be used in controlled mode." + }, + { + "name": "onChange", + "type": "(event: MouseEventHandler, value: string) => void", + "required": false, + "default": null, + "description": "" + }, + { + "name": "shape", + "type": "Shape", + "required": false, + "default": "''", + "description": "Controls the shape of the SegmentPicker and its items.\n(default) - Items stretch to fill the container equally.\n'circle' - Items fit tightly around their content to maintain a circular shape.\nOnly supported when items contain icons only." + }, + { + "name": "size", + "type": "Size", + "required": false, + "default": "'medium'", + "description": "Size variant of the SegmentPicker and its items." + }, + { + "name": "value", + "type": "string", + "required": true, + "default": null, + "description": "The currently selected value (controlled mode)." + } + ], + "cssVariables": [ + { + "name": "--ax-public-segment-picker-border-radius-extra-large", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-large", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-medium", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-small", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-extra-small", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-xx-small", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-border-radius-xxx-small", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-gap", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-padding", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-background-color", + "comment": "" + }, + { + "name": "--ax-public-segment-picker-circle-border-radius", + "comment": "" + } + ] + }, + "select": { + "name": "Select", + "props": [ + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "Custom class name for the component." + }, + { + "name": "defaultValue", + "type": "SelectValueType", + "required": false, + "default": null, + "description": "The default value of the select when uncontrolled." + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the select is disabled." + }, + { + "name": "error", + "type": "boolean", + "required": false, + "default": "false", + "description": "Whether the select has an error" + }, + { + "name": "items", + "type": "SelectItem[]", + "required": true, + "default": null, + "description": "List of items to display in the select dropdown" + }, + { + "name": "name", + "type": "string", + "required": false, + "default": null, + "description": "Identifies the field when a form is submitted." + }, + { + "name": "onChange", + "type": "(event: SyntheticEvent | Event | null, value: SelectValueType) => void", + "required": false, + "default": null, + "description": "Callback fired when the value of the select changes." + }, + { + "name": "placeholder", + "type": "string", + "required": false, + "default": null, + "description": "Placeholder text for the select input" + }, + { + "name": "required", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the user must choose a value before submitting a form." + }, + { + "name": "size", + "type": "ItemSize", + "required": false, + "default": "'medium'", + "description": "Size of the select input" + }, + { + "name": "value", + "type": "SelectValueType", + "required": false, + "default": null, + "description": "The controlled value of the select." + } + ], + "cssVariables": [ + { + "name": "--ax-public-select-border-size", + "comment": "" + }, + { + "name": "--ax-public-select-button-color", + "comment": "" + }, + { + "name": "--ax-public-select-button-border-color", + "comment": "" + }, + { + "name": "--ax-public-select-button-border-color-focus", + "comment": "" + }, + { + "name": "--ax-public-select-button-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-select-button-background-color-error", + "comment": "" + }, + { + "name": "--ax-public-select-button-border-color-error", + "comment": "" + }, + { + "name": "--ax-public-select-button-color-error", + "comment": "" + } + ] + }, + "separator": { + "name": "Separator", + "props": [], + "cssVariables": [ + { + "name": "--ax-public-list-item-separator-border-size", + "comment": "" + }, + { + "name": "--ax-public-list-item-separator-background-color", + "comment": "" + } + ] + }, + "snackbar": { + "name": "Snackbar", + "props": [ + { + "name": "buttonLabel", + "type": "string", + "required": false, + "default": null, + "description": "Label for the action button" + }, + { + "name": "close", + "type": "boolean", + "required": false, + "default": "false", + "description": "Whether to show the close button" + }, + { + "name": "onButtonClick", + "type": "() => void", + "required": false, + "default": null, + "description": "Callback fired when the action button is clicked" + }, + { + "name": "onClose", + "type": "() => void", + "required": false, + "default": null, + "description": "Callback fired when the snackbar is closed" + }, + { + "name": "subtitle", + "type": "string", + "required": false, + "default": null, + "description": "Optional secondary message displayed below the title" + }, + { + "name": "title", + "type": "string", + "required": true, + "default": null, + "description": "Main message displayed in the snackbar" + }, + { + "name": "variant", + "type": "SnackbarVariant", + "required": true, + "default": null, + "description": "Visual style variant of the snackbar" + } + ], + "cssVariables": [ + { + "name": "--ax-public-snackbar-border-size", + "comment": "" + }, + { + "name": "--ax-public-snackbar-padding", + "comment": "" + }, + { + "name": "--ax-public-snackbar-border-radius", + "comment": "" + }, + { + "name": "--ax-public-snackbar-content-gap", + "comment": "" + }, + { + "name": "--ax-public-snackbar-width", + "comment": "" + }, + { + "name": "--ax-public-snackbar-title-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-subtitle-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-close-icon-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-default-border", + "comment": "" + }, + { + "name": "--ax-public-snackbar-default-background", + "comment": "" + }, + { + "name": "--ax-public-snackbar-default-icon-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-success-border", + "comment": "" + }, + { + "name": "--ax-public-snackbar-success-background", + "comment": "" + }, + { + "name": "--ax-public-snackbar-success-icon-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-error-border", + "comment": "" + }, + { + "name": "--ax-public-snackbar-error-background", + "comment": "" + }, + { + "name": "--ax-public-snackbar-error-icon-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-warning-border", + "comment": "" + }, + { + "name": "--ax-public-snackbar-warning-background", + "comment": "" + }, + { + "name": "--ax-public-snackbar-warning-icon-color", + "comment": "" + }, + { + "name": "--ax-public-snackbar-info-border", + "comment": "" + }, + { + "name": "--ax-public-snackbar-info-background", + "comment": "" + }, + { + "name": "--ax-public-snackbar-info-icon-color", + "comment": "" + } + ] + }, + "status": { + "name": "Status", + "props": [ + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "Custom class name for the component." + }, + { + "name": "status", + "type": "ValidationStatus", + "required": false, + "default": null, + "description": "The validation status to display." + } + ], + "cssVariables": [ + { + "name": "--ax-public-status-right", + "comment": "missing token" + }, + { + "name": "--ax-public-status-size", + "comment": "missing token" + }, + { + "name": "--ax-public-status-icon-size", + "comment": "missing token" + }, + { + "name": "--ax-public-status-invalid-color", + "comment": "missing token" + }, + { + "name": "--ax-public-status-invalid-background-color", + "comment": "missing token" + } + ] + }, + "switch": { + "name": "Switch", + "props": [ + { + "name": "checked", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the switch is checked or not" + }, + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "Custom class name for the switch component" + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the switch is disabled" + }, + { + "name": "onChange", + "type": "(checked: boolean, event: Event) => void", + "required": false, + "default": null, + "description": "Callback function when the switch state changes" + }, + { + "name": "size", + "type": "SelectorSize", + "required": false, + "default": "'medium'", + "description": "Size of the switch component" + }, + { + "name": "styles", + "type": "string", + "required": false, + "default": null, + "description": "Custom styles to apply to the switch" + }, + { + "name": "thumbChildren", + "type": "React.ReactNode", + "required": false, + "default": null, + "description": "Custom content for the thumb of the switch" + }, + { + "name": "trackChildren", + "type": "React.ReactNode", + "required": false, + "default": null, + "description": "Custom content for the track of the switch" + } + ], + "cssVariables": [ + { + "name": "--ax-public-icon-switch-width", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-height", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-thumb-size", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-thumb-offset", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-translate", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-padding", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-icon-size", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-track-bg", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-thumb-bg-primary", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-icon-color", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-icon-selected-color", + "comment": "" + }, + { + "name": "--ax-public-icon-switch-thumb-bg-secondary", + "comment": "" + }, + { + "name": "--ax-public-switch-width-medium", + "comment": "" + }, + { + "name": "--ax-public-switch-height-medium", + "comment": "" + }, + { + "name": "--ax-public-switch-thumb-padding-medium", + "comment": "" + }, + { + "name": "--ax-public-switch-width-small", + "comment": "" + }, + { + "name": "--ax-public-switch-height-small", + "comment": "" + }, + { + "name": "--ax-public-switch-thumb-padding-small", + "comment": "" + }, + { + "name": "--ax-public-switch-width-extra-small", + "comment": "" + }, + { + "name": "--ax-public-switch-height-extra-small", + "comment": "" + }, + { + "name": "--ax-public-switch-thumb-padding-extra-small", + "comment": "" + }, + { + "name": "--ax-public-switch-outline-color", + "comment": "" + }, + { + "name": "--ax-public-switch-disabled-background-color", + "comment": "" + }, + { + "name": "--ax-public-switch-disabled-border-color", + "comment": "" + }, + { + "name": "--ax-public-track-no-selected-color", + "comment": "" + }, + { + "name": "--ax-public-track-selected-color", + "comment": "" + }, + { + "name": "--ax-public-thumb-no-selected-color", + "comment": "" + }, + { + "name": "--ax-public-thumb-disabled-color", + "comment": "" + }, + { + "name": "--ax-public-thumb-selected-color", + "comment": "" + }, + { + "name": "--ax-public-switch-focus-border-color", + "comment": "" + }, + { + "name": "--ax-public-switch-border-size", + "comment": "" + } + ] + }, + "text-area": { + "name": "TextArea", + "props": [ + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "Custom class name for the textarea" + }, + { + "name": "defaultValue", + "type": "string", + "required": false, + "default": null, + "description": "Initial value of the textarea" + }, + { + "name": "disabled", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the textarea is disabled" + }, + { + "name": "error", + "type": "boolean", + "required": false, + "default": null, + "description": "Whether the textarea has an error" + }, + { + "name": "maxRows", + "type": "number", + "required": false, + "default": null, + "description": "Maximum number of rows the textarea can expand to" + }, + { + "name": "minRows", + "type": "number", + "required": false, + "default": null, + "description": "Minimum number of rows the textarea can expand to" + }, + { + "name": "onBlur", + "type": "(event: React.FocusEvent) => void", + "required": false, + "default": null, + "description": "Function called when the input loses focus.\nThe event parameter may be undefined." + }, + { + "name": "onChange", + "type": "(event: React.ChangeEvent) => void", + "required": false, + "default": null, + "description": "Callback function to handle change in textarea value" + }, + { + "name": "onClick", + "type": "(event: React.MouseEvent) => void", + "required": false, + "default": null, + "description": "Callback function to handle click" + }, + { + "name": "placeholder", + "type": "string", + "required": false, + "default": null, + "description": "Placeholder text for the textarea" + }, + { + "name": "size", + "type": "ItemSize", + "required": false, + "default": "'medium'", + "description": "Size of the textarea" + }, + { + "name": "spellCheck", + "type": "boolean", + "required": false, + "default": null, + "description": "Enables or disables browser spell checking" + }, + { + "name": "value", + "type": "string", + "required": false, + "default": null, + "description": "Controlled value of the textarea" + } + ], + "cssVariables": [ + { + "name": "--ax-public-textarea-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-color", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-border-color", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-border-color-focus", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-border-size", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-background-color-error", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-border-color-error", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-color-error", + "comment": "" + }, + { + "name": "--ax-public-textarea-root-color-disabled", + "comment": "" + } + ] + }, + "tooltip": { + "name": "Tooltip", + "props": [ + { + "name": "children", + "type": "ReactNode", + "required": true, + "default": null, + "description": "Tooltip reference element." + }, + { + "name": "initialOpen", + "type": "boolean", + "required": false, + "default": null, + "description": "If true, the component is shown at initial" + }, + { + "name": "onOpenChange", + "type": "(open: boolean) => void", + "required": false, + "default": null, + "description": "Callback fired when the component requests to be open." + }, + { + "name": "open", + "type": "boolean", + "required": false, + "default": null, + "description": "If true, the component is shown." + }, + { + "name": "placement", + "type": "TooltipPlacement", + "required": false, + "default": null, + "description": "Tooltip placement." + } + ], + "cssVariables": [ + { + "name": "--ax-public-tooltip-default-background", + "comment": "" + }, + { + "name": "--ax-public-tooltip-blue-background", + "comment": "" + }, + { + "name": "--ax-public-tooltip-padding", + "comment": "" + }, + { + "name": "--ax-public-tooltip-radius-size", + "comment": "" + }, + { + "name": "--ax-public-tooltip-text-color", + "comment": "" + }, + { + "name": "--ax-public-tooltip-blue-text-color", + "comment": "" + } + ] + }, + "node-icon": { + "name": "NodeIcon", + "props": [ + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "icon", + "type": "ReactNode", + "required": true, + "default": null, + "description": "" + } + ], + "cssVariables": [ + { + "name": "--ax-public-node-icon-border-size", + "comment": "" + }, + { + "name": "--ax-public-node-icon-border-radius", + "comment": "" + }, + { + "name": "--ax-public-node-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-node-icon-color", + "comment": "" + }, + { + "name": "--ax-public-node-icon-container-border-color", + "comment": "" + }, + { + "name": "--ax-public-node-icon-container-background-color", + "comment": "" + } + ] + }, + "node-description": { + "name": "NodeDescription", + "props": [ + { + "name": "className", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "description", + "type": "string", + "required": false, + "default": null, + "description": "" + }, + { + "name": "label", + "type": "string", + "required": true, + "default": null, + "description": "" + } + ], + "cssVariables": [ + { + "name": "--ax-public-node-title-color", + "comment": "" + }, + { + "name": "--ax-public-node-title-subtitle", + "comment": "" + } + ] + }, + "node-as-port-wrapper": { + "name": "NodeAsPortWrapper", + "props": [ + { + "name": "isConnecting", + "type": "boolean", + "required": true, + "default": null, + "description": "" + }, + { + "name": "offset", + "type": "{ … }", + "required": false, + "default": null, + "description": "" + }, + { + "name": "targetPortPosition", + "type": "Position", + "required": true, + "default": null, + "description": "" + } + ], + "cssVariables": [] + }, + "edge": { + "name": "EdgeLabel", + "props": [ + { + "name": "isHovered", + "type": "boolean", + "required": false, + "default": null, + "description": "" + }, + { + "name": "size", + "type": "EdgeLabelSize", + "required": false, + "default": null, + "description": "" + }, + { + "name": "state", + "type": "EdgeState", + "required": false, + "default": null, + "description": "The visual state of the edge. Determines base styles like `strokeWidth`." + }, + { + "name": "type", + "type": "EdgeLabelType", + "required": false, + "default": null, + "description": "Determines the layout style for the EdgeLabel based on its content:\n\ntext: Simple text label.\nicon: Single icon without additional content.\ncompound: Mixed content like icons + text, multiple icons, etc." + } + ], + "cssVariables": [ + { + "name": "--ax-public-edge-label-medium-label-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-small-label-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-extra-small-label-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-medium-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-small-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-extra-small-icon-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-medium-content-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-small-content-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-extra-small-content-padding", + "comment": "" + }, + { + "name": "--ax-public-edge-label-medium-border-radius", + "comment": "" + }, + { + "name": "--ax-public-edge-label-small-border-radius", + "comment": "" + }, + { + "name": "--ax-public-edge-label-extra-small-border-radius", + "comment": "" + }, + { + "name": "--ax-public-edge-label-medium-icon-size", + "comment": "" + }, + { + "name": "--ax-public-edge-label-small-icon-size", + "comment": "" + }, + { + "name": "--ax-public-edge-label-extra-small-icon-size", + "comment": "" + }, + { + "name": "--ax-public-edge-label-gap", + "comment": "" + }, + { + "name": "--ax-public-edge-background-color", + "comment": "" + }, + { + "name": "--ax-public-edge-label-color", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-size", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-size-selected", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-style", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-color", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-color-selected", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-color-hover", + "comment": "" + }, + { + "name": "--ax-public-edge-label-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-edge-label-border-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-edge-color-disabled", + "comment": "" + }, + { + "name": "--ax-public-edge-color-select", + "comment": "" + }, + { + "name": "--ax-public-edge-color-hover", + "comment": "" + }, + { + "name": "--ax-public-edge-color", + "comment": "" + }, + { + "name": "--ax-public-edge-stroke-width-select", + "comment": "" + }, + { + "name": "--ax-public-edge-stroke-width", + "comment": "" + } + ] + } +} 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..5a08d16ae 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "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", diff --git a/packages/tokens/config.ts b/packages/tokens/config.ts new file mode 100644 index 000000000..fbe0d5261 --- /dev/null +++ b/packages/tokens/config.ts @@ -0,0 +1,15 @@ +import { Config } from './src/types'; + +export const config: Config = { + primitives: ['numerals-mode-1', 'primitives-mode-1'], + themes: [ + { + name: 'tokens-dark', + selector: "html[data-theme='dark']", + }, + { + name: 'tokens-light', + selector: "html[data-theme='light']", + }, + ], +}; diff --git a/packages/tokens/output.json b/packages/tokens/output.json new file mode 100644 index 000000000..e69de29bb diff --git a/packages/tokens/package.json b/packages/tokens/package.json new file mode 100644 index 000000000..6fbd5791a --- /dev/null +++ b/packages/tokens/package.json @@ -0,0 +1,19 @@ +{ + "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" + }, + "devDependencies": { + "@tokens-studio/sd-transforms": "^1.2.12", + "remeda": "^2.21.2", + "style-dictionary": "^4.3.3", + "tsx": "^4.19.3" + } +} diff --git a/packages/tokens/src/constants.ts b/packages/tokens/src/constants.ts new file mode 100644 index 000000000..8b31425b4 --- /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..34bb87793 --- /dev/null +++ b/packages/tokens/src/eject-tokens.ts @@ -0,0 +1,29 @@ +import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; + +import tokens from '../tokens.json'; +import { TOKEN_OUTPUT_DIR } from './constants'; +import { toFileName } from './to-file-name'; + +export function ejectTokens(): void { + try { + if (!existsSync(TOKEN_OUTPUT_DIR)) { + mkdirSync(TOKEN_OUTPUT_DIR, { recursive: true }); + } + + // Filter out tokens metadata to get themes + const themes = Object.entries(tokens).filter(([name]) => !name.startsWith('$')); + + for (const [name, theme] of themes) { + const fileName = toFileName(name); + + const outputFilePath = path.join(TOKEN_OUTPUT_DIR, `${fileName}.json`); + console.log(outputFilePath); + + // Write the property value to a new JSON file + writeFileSync(outputFilePath, JSON.stringify(theme, null, 2), 'utf8'); + } + } catch (error) { + console.error('Error extracting JSON properties:', error); + } +} diff --git a/packages/tokens/src/generate-css-bundle.ts b/packages/tokens/src/generate-css-bundle.ts new file mode 100644 index 000000000..49f9c1f85 --- /dev/null +++ b/packages/tokens/src/generate-css-bundle.ts @@ -0,0 +1,31 @@ +import { readFile, writeFile } from 'node:fs/promises'; + +import { config } from '../config'; +import { Theme } from './types'; + +const { primitives, themes } = config; + +const codeChunks: string[] = []; + +export async function generateCSSBundle() { + for (const primitive of primitives) { + codeChunks.push(createPrimitiveImport(primitive)); + } + + for (const theme of themes) { + codeChunks.push(await createThemeImport(theme)); + } + + const code = codeChunks.join('\n\n'); + + return writeFile('./dist/tokens.css', code); +} + +function createPrimitiveImport(name: string) { + return `@import "./${name}.css";`; +} + +async function createThemeImport({ name }: Theme) { + const css = await readFile(`./dist/${name}.css`); + return css.toString(); +} diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts new file mode 100644 index 000000000..a1350c326 --- /dev/null +++ b/packages/tokens/src/index.ts @@ -0,0 +1,8 @@ +import { tokensToCss } from './tokens-to-css'; + +import { ejectTokens } from './eject-tokens'; +import { generateCSSBundle } from './generate-css-bundle'; + +ejectTokens(); +await tokensToCss(); +await generateCSSBundle(); 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..019499e18 --- /dev/null +++ b/packages/tokens/src/tokens-to-css.ts @@ -0,0 +1,107 @@ +import { register } from '@tokens-studio/sd-transforms'; +import StyleDictionary, { Config, TransformedToken } from 'style-dictionary'; + +import { config } from '../config'; +import { OUTPUT_DIR, TOKEN_OUTPUT_DIR } from './constants'; +import { toFileName } from './to-file-name'; + +const { primitives, themes } = config; + +register(StyleDictionary); + +export async function tokensToCss() { + const primitiveSourceMap = createPrimitiveSourceMap(); + + await processPrimitiveTokens(primitiveSourceMap); + await processThemeTokens(primitiveSourceMap); +} + +function createPrimitiveSourceMap(): Map { + const sourceMap = new Map(); + for (const tokenSet of primitives) { + sourceMap.set(tokenSet, `${TOKEN_OUTPUT_DIR}${tokenSet}.json`); + } + + return sourceMap; +} + +async function processPrimitiveTokens(primitiveSourceMap: Map): Promise { + for (const primitive of primitives) { + const themeName = toFileName(primitive); + const sourcePath = primitiveSourceMap.get(primitive); + + if (!sourcePath) { + console.warn(`Source path not found for primitive: ${primitive}`); + continue; + } + + const source = [sourcePath]; + + const config = createSDConfig({ + name: themeName, + source, + }); + + const styleDictionary = new StyleDictionary(config); + await styleDictionary.buildAllPlatforms(); + } +} + +async function processThemeTokens(primitiveSourceMap: Map): Promise { + for (const { name, selector } of themes) { + const themeName = toFileName(name); + const primitiveSources = [...primitiveSourceMap.values()]; + const source = [...primitiveSources, `${TOKEN_OUTPUT_DIR}${name}.json`]; + + const config = createSDConfig({ + name: themeName, + source, + selector, + filter: (token) => !primitives.some((primitive) => token.filePath.includes(primitive)), + }); + + const styleDictionary = new StyleDictionary(config); + await styleDictionary.buildAllPlatforms(); + } +} + +function createSDConfig({ name, 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: `${name}.css`, + filter, + format: 'css/variables', + }, + ], + }, + }, + log: logOptions, + } as Config; +} + +type SDConfigParams = { + name: string; + source: string[]; + selector?: string; + filter?: (token: TransformedToken) => boolean; +}; + +const logOptions = { + warnings: 'disabled', // 'warn' | 'error' | 'disabled' + verbosity: 'verbose', // 'default' | 'silent' | 'verbose' + errors: { + brokenReferences: 'console', // '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..8c4808966 --- /dev/null +++ b/packages/tokens/src/types.ts @@ -0,0 +1,9 @@ +export type Config = { + primitives: string[]; + themes: Theme[]; +}; + +export type Theme = { + name: string; + selector: string; +}; 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..9536a0f41 --- /dev/null +++ b/packages/tokens/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.base.json" +} diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md new file mode 100644 index 000000000..c2a89e4ca --- /dev/null +++ b/packages/ui/CHANGELOG.md @@ -0,0 +1,62 @@ +# Changelog + +All notable changes to `@workflowbuilder/ui` are documented in this file. + +## 1.0.0-beta.28 + +This beta moves the library into the Workflow Builder monorepo and publishes it +as `@workflowbuilder/ui` (previously `@synergycodes/overflow-ui`). It rebuilds +the library on [Base UI](https://base-ui.com/) and removes the previous MUI / +Mantine / Emotion / Floating UI stack. It contains breaking changes relative to +`@synergycodes/overflow-ui@1.0.0-beta.27`; read the sections below before +upgrading. + +### Breaking changes + +#### Dependencies + +- Removed `@mui/material`, `@mui/base`, `@mantine/core`, `@mantine/dates`, + `@emotion/*`, and `@floating-ui/react`. +- `@base-ui/react` (`^1.4.0`) is now a **peer dependency** - consumers must + install it alongside this package. +- `DatePicker` is rebuilt on `react-day-picker` + `date-fns`; `TextArea` on + `react-textarea-autosize`. `date-fns`, `react-day-picker`, and `clsx` are + bundled into the package output. + +#### Packaging + +- The build moved from a single bundle to a **multi-entry build** with a + per-component subpath export: `@workflowbuilder/ui/` (e.g. + `@workflowbuilder/ui/date-picker`). +- Importing from the package root still injects all required styles. Importing + per-component entries injects only that component's CSS, so add + `@workflowbuilder/ui/styles.css` once for the global layer order, reset, and + typography. + +#### Component API + +- **DatePicker**: the prop surface no longer forwards the full Mantine prop set. + It now accepts a curated list: `value`, `defaultValue`, `type`, + `valueFormat`, `placeholder`, `error`, `size`, `disabled`, `minDate`, + `maxDate`, `onChange`, `id`, `className`, `aria-label`, `aria-labelledby`. + - `valueFormat` uses `date-fns` tokens (e.g. `dd/MM/yyyy`). The legacy + `DD/MM/YYYY` (dayjs) default is accepted and converted for compatibility. + - In `range` mode, `onChange` fires `null` while a range is mid-selection and + emits the completed `[from, to]` tuple once both ends are picked. +- **Menu**: `onOpenChange` signature is now `(open: boolean, event?: Event)`. + The MUI `slotProps` / passthrough surface is no longer available. +- **Select**: `onChange` signature is `(event, value)` - the event is the first + argument, the selected value the second. +- **Switch**: `onChange` is `(checked: boolean, event: Event)`. The second + argument is the native DOM event (previously typed as a React + `ChangeEvent`, which never matched the value passed at + runtime). +- Transitions moved to the popup element, where Base UI sets + `data-starting-style` / `data-ending-style`. + +### Added + +- `@workflowbuilder/ui/styles.css` - standalone global stylesheet (layer + order, reset, typography) for per-component / subpath consumers. +- `Input` gains a typed `error` prop wired to the error state. +- `Snackbar` exposes proper `role="status"` / `aria-live` semantics. diff --git a/packages/ui/README.md b/packages/ui/README.md new file mode 100644 index 000000000..5c08eb484 --- /dev/null +++ b/packages/ui/README.md @@ -0,0 +1,150 @@ +# @workflowbuilder/ui + +A React library for creating node-based user interfaces and diagram-driven apps. Built to work seamlessly with React Flow, it provides a collection of ready-to-use components and templates that simplify the development of visual editors, workflows, and interactive diagrams. + +Developed and maintained by **[Synergy Codes](https://www.synergycodes.com/)**. + +## Quick Start: 3-Minute Guide + +### 📦 Installation + +Use one of the commands below to add **Overflow UI** to your project: + +```bash +npm install @workflowbuilder/ui +``` + +```bash +pnpm add @workflowbuilder/ui +``` + +```bash +yarn add @workflowbuilder/ui +``` + +### 🎨 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, so import the global stylesheet once and +> **before any component**, so the cascade layers are ordered correctly: +> `import '@workflowbuilder/ui/styles.css'`. + +### 🎛️ 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. Overflow 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 Overflow 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; +} +``` + +### Overflow UI css layers + +Overflow 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 Overflow UI defines, so your styles will always win the specificity war. You can customize Overflow UI components with simple `input {}`. + +```css +@layer ui.component { + .separator { + /* … */ + } +} +``` + +Default Overflow UI order: + +```css +@layer ui.base, ui.component; +``` + +# 🛠️ Development + +To develop and test components, follow these steps in monorepo root: + +1. Install dependencies: + +```bash +pnpm i +``` + +2. Build UI: + +```bash +pnpm ui build +``` + +3. Run preview page: + +```bash +pnpm ui preview +``` + +Now visit `localhost:3000` to see every change in components reflected in the preview page. +Edit `ui/preview-page/preview-page.tsx` to display desired components. + +### 📣 Important Note on Underlying Technology + +> **Overflow 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, Overflow UI provides components that are **accessible by default** and **fully customizable** through our design tokens. +> +> Earlier `1.0.0` betas were built on the now-deprecated [MUI Base](https://v6.mui.com/base-ui/getting-started/). From `1.0.0-beta.28` the library is built on Base UI; see [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. + +## 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..ea8a3ba1b --- /dev/null +++ b/packages/ui/combine-css-bundle.mts @@ -0,0 +1,54 @@ +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). + * + * Per-component stylesheets do not carry the @layer declaration; consumers + * establish the order by importing `styles.css` first (or the barrel). + */ +export function combineCssBundle(rootDirectory: string): Plugin { + const distributionDirectory = path.resolve(rootDirectory, 'dist'); + const stylesDirectory = path.resolve(rootDirectory, 'src/styles'); + + return { + name: 'overflow-ui:combine-css-bundle', + apply: 'build', + closeBundle() { + writeCombinedStylesheet(distributionDirectory, stylesDirectory); + writeGlobalStylesheet(distributionDirectory, stylesDirectory); + }, + }; +} + +function readLayerOrder(stylesDirectory: string): string { + return fs.readFileSync(path.resolve(stylesDirectory, 'layers.css'), 'utf8').trim(); +} + +function writeCombinedStylesheet(distributionDirectory: string, stylesDirectory: string) { + const assetsDirectory = path.resolve(distributionDirectory, 'assets'); + if (!fs.existsSync(assetsDirectory)) return; + + const styles = fs + .readdirSync(assetsDirectory) + .filter((file) => file.endsWith('.css')) + .sort() + .map((file) => fs.readFileSync(path.resolve(assetsDirectory, file), 'utf8')) + .join('\n'); + + // index.css is consumed standalone, so it declares the @layer order itself. + 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); +} diff --git a/packages/ui/css-layers.md b/packages/ui/css-layers.md new file mode 100644 index 000000000..e6c34161a --- /dev/null +++ b/packages/ui/css-layers.md @@ -0,0 +1,32 @@ +# CSS layers + +Overflow UI emits all of its styles into two ordered cascade layers: + +```css +@layer ui.base, ui.component; +``` + +`ui.base` holds resets and primitives; `ui.component` holds component styles. +Declaring the order once, 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. The declaration lives in +`styles.css` (and in the package barrel, which imports it first), so consumers +establish it by either: + +- importing from the package root - the barrel imports the declaration first; or +- importing `@workflowbuilder/ui/styles.css` **before** any component when + using per-component subpath imports. + +Per-component stylesheets deliberately do **not** repeat the declaration. If one +loads before `styles.css`, the first use of a layer fixes the order: a +`ui.component` rule seen before any `ui.base` rule locks it as +`[ui.component, ui.base]`, inverting the cascade. Importing `styles.css` first +avoids this. + +The combined `index.css` (consumed standalone, e.g. by Workflow Builder) carries +the declaration at its top for the same reason. 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/package.json b/packages/ui/package.json new file mode 100644 index 000000000..f1533b2aa --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,94 @@ +{ + "name": "@workflowbuilder/ui", + "type": "module", + "version": "1.0.0-beta.28", + "description": "A React library for creating node-based UIs and diagram-driven applications. Perfect for React Flow users, providing ready-to-use node templates and components that work seamlessly with React Flow's ecosystem.", + "keywords": [ + "react", + "ui", + "diagram", + "node-based", + "components", + "typescript", + "react flow", + "flow", + "node-templates", + "diagram-components" + ], + "author": "Synergy Codes", + "license": "MIT", + "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", + "build:stats": "BUNDLE_STATS=1 vite build" + }, + "module": "./dist/index.js", + "files": [ + "dist", + "README.md", + "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": { + "@phosphor-icons/react": "catalog:", + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "react-day-picker": "^9.14.0", + "react-textarea-autosize": "^8.5.6" + }, + "devDependencies": { + "@base-ui/react": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "^19.1.0", + "@workflowbuilder/ui-tokens": "workspace:*", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.2.0", + "postcss": "^8.5.3", + "react": "catalog:", + "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" + }, + "peerDependencies": { + "@base-ui/react": "^1.4.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "sideEffects": [ + "**/*.css" + ] +} diff --git a/packages/ui/postcss-box-sizing.mts b/packages/ui/postcss-box-sizing.mts new file mode 100644 index 000000000..9a6d0a03d --- /dev/null +++ b/packages/ui/postcss-box-sizing.mts @@ -0,0 +1,42 @@ +import type { Plugin } from 'postcss'; + +/** + * PostCSS plugin that adds `box-sizing: border-box` to every rule + * in CSS Module files (*.module.css). This ensures all library + * components use border-box sizing without leaking styles to users. + */ +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 (rule.selector === ':root') 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/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts new file mode 100644 index 000000000..802f51568 --- /dev/null +++ b/packages/ui/scripts/check-built-css.ts @@ -0,0 +1,59 @@ +/** + * Guard against the WB-222 bug class in built CSS output. + * + * Mirrors the `local/no-invalid-var` stylelint rule but runs after `vite + * build`, so the release fails even if the source-level check is bypassed + * (disabled rule, --no-verify commit, a build-tool transform that + * regresses, etc.). The rule: `var()`'s first argument must be a + * `` (a dashed-ident) — never another function. + * Browsers silently invalidate `var(var(--foo))` and fall back, which is + * how WB-222 shipped a wrong snackbar icon color. + * + * Run after `vite build`. Exits non-zero on any match. + */ +import { globSync, readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); +const distributionDirectory = path.resolve(currentDirectory, '../dist'); + +// Matches `var(` followed by optional whitespace and another `var(` — +// covers the minified `var(var(` and the unminified `var( var(` / +// `var(\n var(`. Comments inside CSS values are uncommon enough that +// false positives aren't a real concern. +const pattern = /var\(\s*var\(/g; + +type Hit = { line: number; column: number; snippet: string }; + +const failures: Array<{ file: string; hits: Hit[] }> = []; + +for (const file of globSync('**/*.css', { cwd: distributionDirectory })) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits: Hit[] = []; + + for (const match of content.matchAll(pattern)) { + const upTo = content.slice(0, match.index); + const line = upTo.split('\n').length; + const column = match.index - upTo.lastIndexOf('\n'); + const snippet = content.slice(Math.max(0, match.index - 12), match.index + 48).replaceAll(/\s+/g, ' '); + hits.push({ line, column, snippet }); + } + + if (hits.length > 0) failures.push({ file, hits }); +} + +if (failures.length === 0) { + console.log('✔ Built CSS: no invalid var(var(...)) calls'); +} else { + const total = failures.reduce((n, f) => n + f.hits.length, 0); + console.error(`\n✖ Found ${total} invalid var(var(...)) call(s) in built CSS output:\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('\nThe first argument of var() must be a --custom-property name. See WB-222.\n'); + 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..097ee827f --- /dev/null +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -0,0 +1,78 @@ +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 ( + +
+
+ {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.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..224c832e8 --- /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 = '', 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..30cfa21d6 --- /dev/null +++ b/packages/ui/src/components/button/regular-button/button.tsx @@ -0,0 +1,104 @@ +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}; + } + + 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..8475b97a3 --- /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 = '', 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..b4479b6d4 --- /dev/null +++ b/packages/ui/src/components/button/types.ts @@ -0,0 +1,10 @@ +import { TooltipVariant } from '../tooltip/types'; + +export type Shape = '' | '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..f62987e9c --- /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 '); +} + +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..236fe2db0 --- /dev/null +++ b/packages/ui/src/components/date-picker/date-picker.module.css @@ -0,0 +1,37 @@ +@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); + } +} 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..2d88a620d --- /dev/null +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -0,0 +1,350 @@ +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'; +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 { 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); + // In-progress range pick ({ from, to: undefined }). The public value + // type can't represent a partial range, so the draft renders the + // first-click highlight until the range completes. + 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], + ); + + // Open the calendar at the month of the current selection (react-day-picker + // does not derive this from `selected` — without it the calendar always + // opens on today's month). + 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); + // A single-date pick is a complete selection — close the + // popover. Range/multiple modes stay open for further picks. + 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) { + // Closing mid-pick abandons the partial range. + 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 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. +} + +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 isDateTuple(value: unknown): value is [Date, Date] { + return Array.isArray(value) && value.length === 2 && value[0] instanceof Date && value[1] instanceof Date; +} + +function toDateRange(value: Date | [Date, Date] | Date[] | null): DateRange | undefined { + if (!isDateTuple(value)) return undefined; + return { from: value[0], to: value[1] }; +} + +const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/; + +/** + * 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. + */ +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; +} + +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..85df37d61 --- /dev/null +++ b/packages/ui/src/components/date-picker/variables.css @@ -0,0 +1,11 @@ +: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-date-outside-color: var(--ax-txt-quaternary-default); + --ax-public-date-picker-date-selected-background-color: var(--ax-ui-bg-tertiary-selected); +} 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..37397433c --- /dev/null +++ b/packages/ui/src/components/edge/edge-label/edge-label-size.module.css @@ -0,0 +1,99 @@ +: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; +} + +.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..e2ac6b2f4 --- /dev/null +++ b/packages/ui/src/components/input/input.tsx @@ -0,0 +1,37 @@ +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 ( +
+ {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..1a0cffcb1 --- /dev/null +++ b/packages/ui/src/components/menu/menu.tsx @@ -0,0 +1,127 @@ +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 { type ComponentProps, ReactElement, memo } from 'react'; + +import listBoxStyles from '@ui/shared/styles/list-box.module.css'; + +import { MenuItem } from './menu-item'; +import { MenuItemProps } from './types'; + +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 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; +}; + +type PositionerProps = ComponentProps; +type PositionerSide = NonNullable; +type PositionerAlign = NonNullable; + +function placementToSideAlign(placement: Placement): { + side: PositionerSide; + align: PositionerAlign; +} { + const [side, alignRaw] = placement.split('-') as [PositionerSide, PositionerAlign | undefined]; + return { side, align: alignRaw ?? 'center' }; +} + +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 }; +} + +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/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..439fa46dc --- /dev/null +++ b/packages/ui/src/components/modal/modal.module.css @@ -0,0 +1,216 @@ +: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 { + .modal-base { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + padding: 1.5rem; + overflow: auto; + + transition: + opacity 200ms ease-out, + transform 200ms ease-out; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + transform: scale(0.96); + } + } + + .backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + inset: 0; + background-color: color-mix(in srgb, var(--ax-public-modal-backdrop-background), transparent 50%); + + transition: opacity 200ms ease-out; + + &[data-starting-style], + &[data-ending-style] { + opacity: 0; + } + } + + .backdrop-close { + all: unset; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .modal { + position: relative; + z-index: 2; + margin: auto; + background-color: var(--ax-public-modal-background); + border-radius: var(--ax-public-modal-border-radius); + width: var(--ax-public-modal-width); + z-index: 1001; + outline: none; + box-shadow: var(--ax-public-modal-box-shadow); + } + + .header { + display: flex; + 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; + background: var(--ax-public-modal-content-background); + border-radius: var(--ax-public-modal-border-radius); + } + + .footer { + display: flex; + 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..3c189da98 --- /dev/null +++ b/packages/ui/src/components/modal/modal.tsx @@ -0,0 +1,111 @@ +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?.(); + } + }} + > + + + +
+
+
+ {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..c1635bf1c --- /dev/null +++ b/packages/ui/src/components/node/node-as-port-wrapper/node-as-port.css @@ -0,0 +1,8 @@ +.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..9cba7887c --- /dev/null +++ b/packages/ui/src/components/node/node-description/node-description.module.css @@ -0,0 +1,25 @@ +:root { + --ax-public-node-title-color: var(--ax-txt-primary-default); + --ax-public-node-title-subtitle: var(--ax-txt-secondary-default); +} + +.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..a42948281 --- /dev/null +++ b/packages/ui/src/components/node/node-icon/node-icon.module.css @@ -0,0 +1,21 @@ +: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); +} + +.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..017cf7757 --- /dev/null +++ b/packages/ui/src/components/node/node-panel/handle.module.css @@ -0,0 +1,96 @@ +: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); +} + +.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%; + } + + :global(.connectingfrom) { + 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 { + 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) { + 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..de6965686 --- /dev/null +++ b/packages/ui/src/components/node/node-panel/node-panel.tsx @@ -0,0 +1,137 @@ +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 (Throws a Runtime Warning):** + * - More than one instance of `NodePanel.Header`, `) { + 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..c6e32352d --- /dev/null +++ b/packages/ui/src/components/radio-button/radio-size.module.css @@ -0,0 +1,39 @@ +: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 */; +} + +.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..d5d6e03f5 --- /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 { MouseEventHandler, 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 || '']), + isSelected: selectedValue === value, + onClick: (event: MouseEventHandler) => 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..122b89c0a --- /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, MouseEventHandler, 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: MouseEventHandler, 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 = '', 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: React.MouseEventHandler, 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..041498703 --- /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 { MouseEventHandler, createContext } from 'react'; + +type SegmentPickerContextType = { + selectedValue: string | undefined; + onSelect: (event: MouseEventHandler, 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.ts b/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts new file mode 100644 index 000000000..c97903da9 --- /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 ''; + } + + 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..c6287fe87 --- /dev/null +++ b/packages/ui/src/components/select/select-button/select-button.module.css @@ -0,0 +1,56 @@ +: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; + 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..ed4d6c8e0 --- /dev/null +++ b/packages/ui/src/components/snackbar/components/action-buttons.module.css @@ -0,0 +1,6 @@ +.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..0070044d2 --- /dev/null +++ b/packages/ui/src/components/snackbar/components/icon.module.css @@ -0,0 +1,31 @@ +.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..b8044d83c --- /dev/null +++ b/packages/ui/src/components/snackbar/components/message.module.css @@ -0,0 +1,16 @@ +.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..9d770eabb --- /dev/null +++ b/packages/ui/src/components/snackbar/snackbar.tsx @@ -0,0 +1,62 @@ +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. + * The snackbar appears at the bottom of the screen and automatically disappears after a few seconds. + */ +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..7996bb1a6 --- /dev/null +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.module.css @@ -0,0 +1,92 @@ +: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)); + } +} 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..15ec21ba3 --- /dev/null +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx @@ -0,0 +1,47 @@ +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'; + +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={ +
+
+ {!checked && icon} + {checked && 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..31c4d9a42 --- /dev/null +++ b/packages/ui/src/components/switch/switch.tsx @@ -0,0 +1,87 @@ +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 styles to apply to the switch + */ + styles?: string; + /** + * 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, + styles, + 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..33a18846c --- /dev/null +++ b/packages/ui/src/components/text-area/text-area.module.css @@ -0,0 +1,62 @@ +: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; + font: 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..bc0418b15 --- /dev/null +++ b/packages/ui/src/components/text-area/text-area.tsx @@ -0,0 +1,116 @@ +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 ( +
+ +
+ ); +} 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/tooltip-content.tsx b/packages/ui/src/components/tooltip/tooltip-content.tsx new file mode 100644 index 000000000..f5ef1d606 --- /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'; +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-trigger.tsx b/packages/ui/src/components/tooltip/tooltip-trigger.tsx new file mode 100644 index 000000000..8c9515876 --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip-trigger.tsx @@ -0,0 +1,52 @@ +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 { useTooltipDelay } from './tooltip'; + +/** + * 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) { + const { delay, closeDelay } = useTooltipDelay(); + + return ( + } + delay={delay} + closeDelay={closeDelay} + // Keep parity with the previous Floating UI behaviour: clicking the + // trigger should not dismiss the tooltip while hover is still active. + closeOnClick={false} + render={(triggerProps, state) => { + const dataState = state.open ? 'open' : 'closed'; + + if (asChild && isValidElement(children)) { + const childElement = children as ReactElement>; + // mergeProps composes event handlers (all of them run) and merges + // className/style — a plain spread would let a child's own + // onMouseEnter/onFocus silently replace Base UI's interaction + // handlers 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..8b8edad36 --- /dev/null +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -0,0 +1,115 @@ +import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; +import { ReactNode, createContext, useContext, useMemo } from 'react'; + +import { TooltipContent } from './tooltip-content'; +import { TooltipTrigger } from './tooltip-trigger'; + +const TOOLTIP_OPEN_DELAY = 500; +const TOOLTIP_CLOSE_DELAY = 0; + +type Side = 'top' | 'right' | 'bottom' | 'left'; +type Align = 'start' | 'end'; + +export type TooltipPlacement = Side | `${Side}-${Align}`; + +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 PlacementContextValue = { + side: Side; + align: 'start' | 'center' | 'end'; +}; + +const TooltipPlacementContext = createContext({ + side: 'bottom', + align: 'center', +}); + +export function useTooltipPlacement(): PlacementContextValue { + return useContext(TooltipPlacementContext); +} + +function placementToSideAlign(placement: TooltipPlacement): PlacementContextValue { + const [side, align] = placement.split('-') as [ + PlacementContextValue['side'], + PlacementContextValue['align'] | undefined, + ]; + return { side, align: align ?? 'center' }; +} + +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} + + + ); +} + +const TooltipDelayContext = createContext<{ + delay: number; + closeDelay: number; +}>({ + delay: TOOLTIP_OPEN_DELAY, + closeDelay: TOOLTIP_CLOSE_DELAY, +}); + +export function useTooltipDelay() { + return useContext(TooltipDelayContext); +} + +function TooltipDelayApplier({ children }: { children: ReactNode }) { + return ( + + {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..148b0cf48 --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1,30 @@ +// [TODO] Improvement: Should we automate it? +// Using glob library could help us generate this entrypoint and imports +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'; 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..221bb7702 --- /dev/null +++ b/packages/ui/src/shared/styles/list-item.module.css @@ -0,0 +1,68 @@ +: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); + + /* Base UI items expose state as data attributes (data-selected, + data-disabled, data-highlighted) — not the MUI-era base--* classes. */ + &:hover:not([data-disabled]) { + 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); + 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.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..20032f81c --- /dev/null +++ b/packages/ui/src/styles/globals.css @@ -0,0 +1,3 @@ +: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..342c26d82 --- /dev/null +++ b/packages/ui/src/styles/typography.css @@ -0,0 +1,167 @@ +/* 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..3ee5e816d --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "paths": { + "@ui/*": ["./src/*"] + } + }, + "include": ["src", "types.d.ts"], + "exclude": ["node_modules", "dist"] +} 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..8def392e4 --- /dev/null +++ b/packages/ui/vite.config.mts @@ -0,0 +1,126 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; +import { libInjectCss } from 'vite-plugin-lib-inject-css'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; +import dts from 'vite-plugin-dts'; +import { visualizer } from 'rollup-plugin-visualizer'; +import { boxSizingPlugin } from './postcss-box-sizing.mts'; +import { combineCssBundle } from './combine-css-bundle.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', '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']; + return viteStaticCopy({ + targets: files.map((file) => ({ + src: `../tokens/dist/${file}`, + dest: '.', + })), + }); +} + +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: 'Overflow 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()], + }, + }, + resolve: { + alias: { + '@ui': path.resolve(rootDirectory,'./src'), + }, + }, + plugins: [ + libInjectCss(), + // Per-entry .d.ts; rollupTypes is intentionally off (incompatible with this + // multi-entry setup, see vite-plugin-dts docs). + dts({ entryRoot: 'src' }), + copyTokenStyles(), + combineCssBundle(rootDirectory), + ...(process.env.BUNDLE_STATS ? bundleStatsPlugins() : []), + ], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0812c5f9f..8fe65011b 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 @@ -95,12 +101,18 @@ importers: apps/ai-studio: dependencies: + '@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) + 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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@workflow-builder/types': specifier: workspace:* version: link:../../packages/types @@ -110,36 +122,18 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 - html-to-image: - specifier: 1.11.11 - version: 1.11.11 immer: specifier: ^10.1.1 version: 10.1.1 - mermaid: - specifier: ^11.15.0 - version: 11.15.0 react: specifier: ^19.1.0 version: 19.1.0 react-dom: specifier: 'catalog:' version: 19.1.0(react@19.1.0) - 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-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.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) - 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:' @@ -155,7 +149,7 @@ importers: 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-svgr: specifier: ^4.3.0 - version: 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)) + version: 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)) 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) @@ -165,9 +159,6 @@ importers: '@hono/node-server': specifier: ^1.14.0 version: 1.19.14(hono@4.12.14) - '@openrouter/ai-sdk-provider': - specifier: ^2.8.0 - version: 2.8.0(ai@6.0.168(zod@4.3.6))(zod@4.3.6) '@temporalio/client': specifier: ^1.11.0 version: 1.16.0 @@ -177,9 +168,6 @@ importers: '@workflow-builder/types': specifier: workspace:* version: link:../../packages/types - ai: - specifier: ^6.0.168 - version: 6.0.168(zod@4.3.6) dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -192,9 +180,6 @@ importers: postgres: specifier: ^3.4.5 version: 3.4.9 - tsx: - specifier: ^4.19.3 - version: 4.21.0 zod: specifier: ^4.3.6 version: 4.3.6 @@ -211,6 +196,12 @@ importers: apps/demo: dependencies: + '@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 @@ -219,7 +210,7 @@ importers: 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) + 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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@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) @@ -238,6 +229,9 @@ importers: i18next: specifier: ^24.2.3 version: 24.2.3(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.0.5 + version: 8.0.5 immer: specifier: ^10.1.1 version: 10.1.1 @@ -264,7 +258,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 @@ -378,9 +372,6 @@ importers: postgres: specifier: ^3.4.5 version: 3.4.9 - tsx: - specifier: ^4.19.3 - version: 4.21.0 devDependencies: '@types/node': specifier: ^22.12.0 @@ -444,18 +435,12 @@ importers: '@fontsource/poppins': specifier: ^5.2.7 version: 5.2.7 - '@jsonforms/core': - specifier: ^3.4.0 - version: 3.5.1 - '@jsonforms/react': - specifier: ^3.4.0 - 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) + 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.13)(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) @@ -471,15 +456,6 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - i18next: - specifier: ^24.0.0 - version: 24.2.3(typescript@5.9.3) - i18next-browser-languagedetector: - specifier: ^8.0.0 - version: 8.0.5 - immer: - specifier: ^10.0.0 - version: 10.1.1 notistack: specifier: ^3.0.1 version: 3.0.2(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -492,9 +468,6 @@ importers: react-dom: specifier: ^18.0.0 || ^19.0.0 version: 19.1.0(react@19.1.0) - react-i18next: - specifier: ^15.0.0 - 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-mentions-ts: specifier: ^5.4.7 version: 5.4.7(class-variance-authority@0.7.1)(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwind-merge@3.5.0) @@ -502,6 +475,12 @@ importers: specifier: ^2.19.2 version: 2.20.0 devDependencies: + '@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) '@testing-library/react': specifier: ^16.3.0 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.1(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -526,6 +505,18 @@ importers: eslint-plugin-tsdoc: specifier: ^0.4.0 version: 0.4.0 + i18next: + specifier: ^24.2.3 + version: 24.2.3(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.0.5 + version: 8.0.5 + immer: + specifier: ^10.1.1 + version: 10.1.1 + 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) vite: specifier: ^6.0.7 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) @@ -540,10 +531,92 @@ 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 packages/types: {} + packages/ui: + dependencies: + '@phosphor-icons/react': + specifier: 'catalog:' + version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + react-day-picker: + specifier: ^9.14.0 + version: 9.14.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: + '@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) + '@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 + 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-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)) + packages: '@ai-sdk/gateway@3.0.104': @@ -575,6 +648,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 +847,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,8 +867,44 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@braintree/sanitize-url@7.1.2': - resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + '@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 + + '@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==} @@ -797,9 +968,6 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@chevrotain/types@11.1.2': - resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@commitlint/cli@21.0.1': resolution: {integrity: sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==} engines: {node: '>=22.12.0'} @@ -913,6 +1081,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 +1689,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,9 +1775,6 @@ 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==} - '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -1738,6 +1921,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,9 +2115,6 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} - '@mermaid-js/parser@1.1.1': - resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} - '@microsoft/api-extractor-model@7.33.8': resolution: {integrity: sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==} @@ -2182,6 +2366,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} @@ -2219,17 +2407,6 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@reduxjs/toolkit@2.12.0': - resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -2438,9 +2615,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -2602,6 +2776,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 +2831,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'} @@ -2699,99 +2886,24 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - - '@types/d3-axis@3.0.6': - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - - '@types/d3-brush@3.0.6': - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - - '@types/d3-chord@3.0.6': - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - '@types/d3-contour@3.0.6': - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - - '@types/d3-delaunay@6.0.4': - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - - '@types/d3-dispatch@3.0.7': - resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} - '@types/d3-drag@3.0.7': resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - '@types/d3-dsv@3.0.7': - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-fetch@3.0.7': - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - - '@types/d3-force@3.0.10': - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - - '@types/d3-format@3.0.4': - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - - '@types/d3-geo@3.1.0': - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} - - '@types/d3-hierarchy@3.1.7': - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-polygon@3.0.2': - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - - '@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-scale-chromatic@3.1.0': - resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} - - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - '@types/d3-shape@3.1.8': - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - - '@types/d3-time-format@4.0.3': - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - '@types/d3-transition@3.0.9': resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} '@types/d3-zoom@3.0.8': resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - '@types/d3@7.4.3': - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2816,9 +2928,6 @@ packages: '@types/express@5.0.5': resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} - '@types/geojson@7946.0.16': - resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -2910,9 +3019,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@types/validator@13.15.4': resolution: {integrity: sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==} @@ -2970,9 +3076,6 @@ packages: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher - '@upsetjs/venn.js@2.0.0': - resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - '@vercel/oidc@3.2.0': resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} @@ -3118,11 +3221,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 +3426,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 +3496,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 +3514,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 +3559,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 +3582,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 +3594,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 +3635,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 +3671,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 +3691,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 +3757,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 +3775,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'} @@ -3637,10 +3790,6 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -3650,6 +3799,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==} @@ -3712,12 +3865,6 @@ packages: core-js@3.46.0: resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} - cose-base@1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} - - cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - cosmiconfig-typescript-loader@6.3.0: resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} engines: {node: '>=v18'} @@ -3799,51 +3946,10 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - cytoscape-cose-bilkent@4.1.0: - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape-fcose@2.2.0: - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape@3.34.0: - resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} - engines: {node: '>=0.10'} - - d3-array@2.12.1: - resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} - - d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - - d3-axis@3.0.0: - resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} - engines: {node: '>=12'} - - d3-brush@3.0.0: - resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} - engines: {node: '>=12'} - - d3-chord@3.0.1: - resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} - engines: {node: '>=12'} - d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} - d3-contour@4.0.2: - resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} - engines: {node: '>=12'} - - d3-delaunay@6.0.4: - resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} - engines: {node: '>=12'} - d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} @@ -3852,88 +3958,18 @@ packages: resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} engines: {node: '>=12'} - d3-dsv@3.0.1: - resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} - engines: {node: '>=12'} - hasBin: true - d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} - d3-fetch@3.0.1: - resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} - engines: {node: '>=12'} - - d3-force@3.0.0: - resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} - engines: {node: '>=12'} - - d3-format@3.1.2: - resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} - engines: {node: '>=12'} - - d3-geo@3.1.1: - resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} - engines: {node: '>=12'} - - d3-hierarchy@3.1.2: - resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} - engines: {node: '>=12'} - d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} - d3-path@1.0.9: - resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} - - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-polygon@3.0.1: - resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} - engines: {node: '>=12'} - - d3-quadtree@3.0.1: - resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} - engines: {node: '>=12'} - - d3-random@3.0.1: - resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} - engines: {node: '>=12'} - - d3-sankey@0.12.3: - resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} - - d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} - engines: {node: '>=12'} - - d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} - d3-shape@1.3.7: - resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - - d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - - d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} @@ -3948,13 +3984,6 @@ packages: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} engines: {node: '>=12'} - d3@7.9.0: - resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} - engines: {node: '>=12'} - - dagre-d3-es@7.0.14: - resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -3971,11 +4000,14 @@ 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==} - dayjs@1.11.21: - resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -3998,9 +4030,6 @@ packages: supports-color: optional: true - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -4014,10 +4043,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'} @@ -4025,9 +4070,6 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delaunator@5.1.0: - resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} - delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4105,9 +4147,6 @@ 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==} - domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -4226,6 +4265,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 +4286,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 +4437,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 +4574,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 +4665,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 +4690,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 +4711,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 +4761,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 +4809,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'} @@ -4776,9 +4856,6 @@ packages: h3@1.15.5: resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} - hachure-fill@0.5.2: - resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -4894,9 +4971,6 @@ packages: html-to-image@1.11.11: resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==} - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -4960,6 +5034,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,9 +5044,6 @@ packages: immer@10.1.1: resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} - immer@11.1.8: - resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} - import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -4989,6 +5063,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==} @@ -5003,13 +5080,6 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - internmap@1.0.1: - resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - - internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - inversify@6.0.1: resolution: {integrity: sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ==} @@ -5052,6 +5122,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 +5156,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 +5197,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 +5210,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 +5283,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 +5301,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 +5383,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 +5404,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==} @@ -5314,15 +5414,11 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - katex@0.16.47: - resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} - hasBin: true - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - 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==} @@ -5347,12 +5443,6 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - layout-base@1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} - - layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} @@ -5484,11 +5574,6 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@16.4.2: - resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} - engines: {node: '>= 20'} - hasBin: true - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5586,9 +5671,6 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.15.0: - resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -5864,6 +5946,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 +5981,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 +6039,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 +6055,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,12 +6107,14 @@ 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==} - path-data-parser@0.1.0: - resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -6023,6 +6126,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 +6137,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==} @@ -6080,16 +6193,14 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - points-on-curve@0.2.0: - resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} - - points-on-path@0.2.1: - resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - possible-typed-array-names@1.0.0: 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 +6215,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 +6226,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 +6293,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 +6330,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: @@ -6236,12 +6363,6 @@ packages: react-is@19.0.0: resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - react-mentions-ts@5.4.7: resolution: {integrity: sha512-bTK6joPmyvLckVf1v7vE2xSSeqvL4ZwuzFvGZpt+IrtdDOdFZjiwTUBo5920kiE6WbH/v1PP81xAo6pQ0NQ0Pg==} engines: {node: '>=20'} @@ -6258,18 +6379,6 @@ packages: 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: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -6324,6 +6433,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,14 +6445,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - recharts@3.9.0: - resolution: {integrity: sha512-dCEcE9y20c8H2tkVeByrAXhhnBJk6/QLbxKmn+dJUptOfc5NMjwRh1jo0vZPRLD+5dMrHrP+hPEsfbGBMfnf5Q==} - engines: {node: '>=18'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -6354,14 +6459,6 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -6445,6 +6542,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==} @@ -6509,17 +6610,24 @@ packages: resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} engines: {node: '>= 0.8.15'} - 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'} hasBin: true - roughjs@4.6.6: - resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -6527,12 +6635,13 @@ 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==} - rw@1.3.3: - resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -6666,6 +6775,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 +6871,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 +6885,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 +6912,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 +6945,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==} @@ -6831,9 +6959,6 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - stylis@4.4.0: - resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} - suf-log@2.5.3: resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} @@ -6930,12 +7055,12 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - 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 +7091,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'} @@ -7004,10 +7133,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-dedent@2.3.0: - resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} - engines: {node: '>=6.10'} - tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -7242,6 +7367,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 +7423,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==} @@ -7324,9 +7461,6 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite-bundle-analyzer@0.17.1: resolution: {integrity: sha512-ubjLhkuRgOSBNck+6xBbQmjmh8SeLTG4alEM5PX2TNzyGhKLwWlyCz1YG0an3RQnscbhVzSb6kYteoHXhP///A==} hasBin: true @@ -7345,6 +7479,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 +7757,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 +7780,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'} @@ -7792,20 +7945,59 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@antfu/install-pkg@1.1.0': - dependencies: - package-manager-detector: 1.6.0 - tinyexec: 1.0.2 + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@antfu/utils@8.1.1': {} + + '@asamuzakjp/css-color@2.8.3': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@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 - '@antfu/utils@8.1.1': {} + '@ast-grep/napi-win32-x64-msvc@0.36.3': + optional: true - '@asamuzakjp/css-color@2.8.3': - dependencies: - '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) - '@csstools/css-tokenizer': 3.0.3 - lru-cache: 10.4.3 + '@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: @@ -8056,6 +8248,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,7 +8278,61 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@braintree/sanitize-url@7.1.2': {} + '@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 + + '@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: @@ -8235,8 +8483,6 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@chevrotain/types@11.1.2': {} - '@commitlint/cli@21.0.1(@types/node@22.12.0)(conventional-commits-parser@6.4.0)(typescript@5.6.3)': dependencies: '@commitlint/format': 21.0.1 @@ -8375,6 +8621,8 @@ snapshots: '@ctrl/tinycolor@4.2.0': {} + '@date-fns/tz@1.5.0': {} + '@drizzle-team/brocli@0.10.2': {} '@emmetio/abbreviation@2.3.3': @@ -8821,17 +9069,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 +9103,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,12 +9179,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/utils@3.1.3': - dependencies: - '@antfu/install-pkg': 1.1.0 - '@iconify/types': 2.0.0 - import-meta-resolve: 4.2.0 - '@img/colour@1.0.0': optional: true @@ -9024,6 +9283,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 @@ -9214,12 +9482,12 @@ snapshots: 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)': + '@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.13)(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 + dayjs: 1.11.13 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -9273,10 +9541,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@mermaid-js/parser@1.1.1': - dependencies: - '@chevrotain/types': 11.1.2 - '@microsoft/api-extractor-model@7.33.8(@types/node@22.12.0)': dependencies: '@microsoft/tsdoc': 0.16.0 @@ -9510,6 +9774,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': {} @@ -9537,18 +9804,6 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@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)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.8 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.2.0 - optionalDependencies: - react: 19.1.0 - react-redux: 9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1) - '@rollup/pluginutils@5.3.0(rollup@4.57.1)': dependencies: '@types/estree': 1.0.8 @@ -9730,8 +9985,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@standard-schema/utils@0.3.0': {} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.7)': dependencies: '@babel/core': 7.26.7 @@ -9883,12 +10136,12 @@ 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)': + '@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.13)(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) + '@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.13)(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) @@ -9903,6 +10156,8 @@ snapshots: - dayjs - supports-color + '@tabby_ai/hijri-converter@1.0.5': {} + '@temporalio/activity@1.16.0': dependencies: '@temporalio/client': 1.16.0 @@ -10002,6 +10257,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 @@ -10055,81 +10322,18 @@ snapshots: dependencies: '@types/node': 22.12.0 - '@types/d3-array@3.2.2': {} - - '@types/d3-axis@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-brush@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-chord@3.0.6': {} - '@types/d3-color@3.1.3': {} - '@types/d3-contour@3.0.6': - dependencies: - '@types/d3-array': 3.2.2 - '@types/geojson': 7946.0.16 - - '@types/d3-delaunay@6.0.4': {} - - '@types/d3-dispatch@3.0.7': {} - '@types/d3-drag@3.0.7': dependencies: '@types/d3-selection': 3.0.11 - '@types/d3-dsv@3.0.7': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-fetch@3.0.7': - dependencies: - '@types/d3-dsv': 3.0.7 - - '@types/d3-force@3.0.10': {} - - '@types/d3-format@3.0.4': {} - - '@types/d3-geo@3.1.0': - dependencies: - '@types/geojson': 7946.0.16 - - '@types/d3-hierarchy@3.1.7': {} - '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 - '@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-scale-chromatic@3.1.0': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - '@types/d3-selection@3.0.11': {} - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time-format@4.0.3': {} - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - '@types/d3-transition@3.0.9': dependencies: '@types/d3-selection': 3.0.11 @@ -10139,39 +10343,6 @@ snapshots: '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 - '@types/d3@7.4.3': - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.7 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@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-scale': 4.0.9 - '@types/d3-scale-chromatic': 3.1.0 - '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.9 - '@types/d3-zoom': 3.0.8 - '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -10207,8 +10378,6 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 1.15.10 - '@types/geojson@7946.0.16': {} - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -10294,8 +10463,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/use-sync-external-store@0.0.6': {} - '@types/validator@13.15.4': {} '@types/yauzl@2.10.3': @@ -10382,11 +10549,6 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@upsetjs/venn.js@2.0.0': - optionalDependencies: - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - '@vercel/oidc@3.2.0': {} '@vitejs/plugin-react@4.3.4(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))': @@ -10508,6 +10670,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 +10801,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 +11003,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: {} @@ -10992,6 +11179,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 +11195,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 +11260,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 +11280,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 +11297,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 +11341,8 @@ snapshots: chance@1.1.9: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -11170,6 +11382,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 +11406,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 +11465,8 @@ snapshots: colorette@2.0.20: {} + colorjs.io@0.5.2: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -11249,14 +11477,14 @@ snapshots: commander@11.1.0: {} + commander@12.1.0: {} + commander@13.1.0: {} commander@2.20.3: {} commander@7.2.0: {} - commander@8.3.0: {} - common-ancestor-path@1.0.1: {} compare-func@2.0.0: @@ -11266,6 +11494,8 @@ snapshots: compare-versions@6.1.1: {} + component-emitter@2.0.0: {} + concat-map@0.0.1: {} concurrently@9.1.2: @@ -11320,14 +11550,6 @@ snapshots: core-js@3.46.0: optional: true - cose-base@1.0.3: - dependencies: - layout-base: 1.0.2 - - cose-base@2.2.0: - dependencies: - layout-base: 2.0.1 - cosmiconfig-typescript-loader@6.3.0(@types/node@22.12.0)(cosmiconfig@9.0.1(typescript@5.6.3))(typescript@5.6.3): dependencies: '@types/node': 22.12.0 @@ -11427,50 +11649,8 @@ snapshots: csstype@3.1.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): - dependencies: - cose-base: 1.0.3 - cytoscape: 3.34.0 - - cytoscape-fcose@2.2.0(cytoscape@3.34.0): - dependencies: - cose-base: 2.2.0 - cytoscape: 3.34.0 - - cytoscape@3.34.0: {} - - d3-array@2.12.1: - dependencies: - internmap: 1.0.1 - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-axis@3.0.0: {} - - d3-brush@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - d3-chord@3.0.1: - dependencies: - d3-path: 3.1.0 - d3-color@3.1.0: {} - d3-contour@4.0.2: - dependencies: - d3-array: 3.2.4 - - d3-delaunay@6.0.4: - dependencies: - delaunator: 5.1.0 - d3-dispatch@3.0.1: {} d3-drag@3.0.0: @@ -11478,82 +11658,14 @@ snapshots: d3-dispatch: 3.0.1 d3-selection: 3.0.0 - d3-dsv@3.0.1: - dependencies: - commander: 7.2.0 - iconv-lite: 0.6.3 - rw: 1.3.3 - d3-ease@3.0.1: {} - d3-fetch@3.0.1: - dependencies: - d3-dsv: 3.0.1 - - d3-force@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-quadtree: 3.0.1 - d3-timer: 3.0.1 - - d3-format@3.1.2: {} - - d3-geo@3.1.1: - dependencies: - d3-array: 3.2.4 - - d3-hierarchy@3.1.2: {} - d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 - d3-path@1.0.9: {} - - d3-path@3.1.0: {} - - d3-polygon@3.0.1: {} - - d3-quadtree@3.0.1: {} - - d3-random@3.0.1: {} - - d3-sankey@0.12.3: - dependencies: - d3-array: 2.12.1 - d3-shape: 1.3.7 - - d3-scale-chromatic@3.1.0: - dependencies: - d3-color: 3.1.0 - d3-interpolate: 3.0.1 - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - d3-selection@3.0.0: {} - d3-shape@1.3.7: - dependencies: - d3-path: 1.0.9 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - d3-timer@3.0.1: {} d3-transition@3.0.1(d3-selection@3.0.0): @@ -11573,44 +11685,6 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - d3@7.9.0: - dependencies: - d3-array: 3.2.4 - d3-axis: 3.0.0 - d3-brush: 3.0.0 - d3-chord: 3.0.1 - d3-color: 3.1.0 - d3-contour: 4.0.2 - d3-delaunay: 6.0.4 - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-dsv: 3.0.1 - d3-ease: 3.0.1 - d3-fetch: 3.0.1 - d3-force: 3.0.0 - d3-format: 3.1.2 - d3-geo: 3.1.1 - d3-hierarchy: 3.1.2 - d3-interpolate: 3.0.1 - d3-path: 3.1.0 - d3-polygon: 3.0.1 - d3-quadtree: 3.0.1 - d3-random: 3.0.1 - d3-scale: 4.0.2 - d3-scale-chromatic: 3.1.0 - d3-selection: 3.0.0 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - d3-timer: 3.0.1 - d3-transition: 3.0.1(d3-selection@3.0.0) - d3-zoom: 3.0.0 - - dagre-d3-es@7.0.14: - dependencies: - d3: 7.9.0 - lodash-es: 4.17.21 - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -11634,9 +11708,11 @@ 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: {} + dayjs@1.11.13: {} de-indent@1.0.2: {} @@ -11648,17 +11724,24 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js-light@2.5.1: {} - decimal.js@10.5.0: {} decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 - deep-eql@5.0.2: {} + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} - deep-is@0.1.4: {} + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 define-data-property@1.1.4: dependencies: @@ -11666,6 +11749,8 @@ snapshots: 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 @@ -11674,10 +11759,6 @@ snapshots: defu@6.1.4: {} - delaunator@5.1.0: - dependencies: - robust-predicates: 3.0.3 - delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -11743,10 +11824,6 @@ snapshots: '@types/trusted-types': 2.0.7 optional: true - dompurify@3.4.11: - optionalDependencies: - '@types/trusted-types': 2.0.7 - domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -11784,6 +11861,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 +11878,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} encoding-sniffer@0.2.1: @@ -12074,6 +12155,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 +12181,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 +12360,8 @@ snapshots: expect-type@1.1.0: {} + expr-eval-fork@2.0.2: {} + express@5.1.0: dependencies: accepts: 2.0.0 @@ -12381,6 +12490,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 +12515,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 +12534,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 +12595,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 +12653,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 @@ -12562,8 +12708,6 @@ snapshots: ufo: 1.6.3 uncrypto: 0.1.3 - hachure-fill@0.5.2: {} - has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -12797,8 +12941,6 @@ snapshots: html-to-image@1.11.11: {} - html-url-attributes@3.0.1: {} - html-void-elements@3.0.0: {} html-whitespace-sensitive-tag-names@3.0.1: {} @@ -12868,12 +13010,12 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} immer@10.1.1: {} - immer@11.1.8: {} - import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -12887,6 +13029,8 @@ snapshots: indent-string@5.0.0: {} + inherits@2.0.3: {} + inherits@2.0.4: {} ini@6.0.0: {} @@ -12899,10 +13043,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - internmap@1.0.1: {} - - internmap@2.0.3: {} - inversify@6.0.1: {} iobuffer@5.4.0: {} @@ -12945,6 +13085,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 +13119,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -13004,12 +13150,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 +13225,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 +13246,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 +13353,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 +13377,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonify@0.0.1: {} + jspdf@3.0.3: dependencies: '@babel/runtime': 7.27.0 @@ -13227,15 +13397,13 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 - katex@0.16.47: - dependencies: - commander: 8.3.0 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - khroma@2.1.0: {} + klaw-sync@6.0.0: + dependencies: + graceful-fs: 4.2.11 kleur@3.0.3: {} @@ -13263,10 +13431,6 @@ snapshots: kolorist@1.8.0: {} - layout-base@1.0.2: {} - - layout-base@2.0.1: {} - levn@0.3.0: dependencies: prelude-ls: 1.1.2 @@ -13402,8 +13566,6 @@ snapshots: marked@15.0.12: {} - marked@16.4.2: {} - math-intrinsics@1.1.0: {} md5@2.3.0: @@ -13630,30 +13792,6 @@ snapshots: merge2@1.4.1: {} - mermaid@11.15.0: - dependencies: - '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.3 - '@mermaid-js/parser': 1.1.1 - '@types/d3': 7.4.3 - '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.34.0 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) - cytoscape-fcose: 2.2.0(cytoscape@3.34.0) - d3: 7.9.0 - d3-sankey: 0.12.3 - dagre-d3-es: 7.0.14 - dayjs: 1.11.21 - dompurify: 3.4.11 - es-toolkit: 1.46.1 - katex: 0.16.47 - khroma: 2.1.0 - marked: 16.4.2 - roughjs: 4.6.6 - stylis: 4.4.0 - ts-dedent: 2.3.0 - uuid: 11.1.0 - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -14072,6 +14210,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 +14259,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 +14343,8 @@ snapshots: p-map@2.1.0: {} + p-map@7.0.4: {} + p-queue@8.1.1: dependencies: eventemitter3: 5.0.1 @@ -14193,6 +14354,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,9 +14428,24 @@ snapshots: parseurl@1.3.3: {} - path-browserify@1.0.1: {} + 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-data-parser@0.1.0: {} + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -14275,10 +14453,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: {} @@ -14316,14 +14506,11 @@ snapshots: pluralize@8.0.0: {} - points-on-curve@0.2.0: {} + possible-typed-array-names@1.0.0: {} - points-on-path@0.2.1: + postcss-calc-ast-parser@0.1.4: dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - - possible-typed-array-names@1.0.0: {} + postcss-value-parser: 3.3.1 postcss-nested@6.2.0(postcss@8.5.6): dependencies: @@ -14340,6 +14527,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 +14537,8 @@ snapshots: postgres@3.4.9: {} + powershell-utils@0.1.0: {} + prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -14416,6 +14607,8 @@ snapshots: punycode.js@2.3.1: {} + punycode@1.4.1: {} + punycode@2.3.1: {} qs@6.14.0: @@ -14452,6 +14645,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 @@ -14472,24 +14673,6 @@ snapshots: react-is@19.0.0: {} - react-markdown@10.1.0(@types/react@19.1.8)(react@19.1.0): - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@types/react': 19.1.8 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.1.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - react-mentions-ts@5.4.7(class-variance-authority@0.7.1)(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwind-merge@3.5.0): dependencies: class-variance-authority: 0.7.1 @@ -14503,15 +14686,6 @@ snapshots: 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 - react: 19.1.0 - use-sync-external-store: 1.4.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.8 - redux: 5.0.1 - react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -14568,30 +14742,14 @@ 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): - 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 - 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) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux - recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -14621,12 +14779,6 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - reflect-metadata@0.1.13: {} reflect.getprototypeof@1.0.10: @@ -14782,6 +14934,8 @@ snapshots: dependencies: type-fest: 4.33.0 + remeda@2.39.0: {} + request-light@0.5.8: {} request-light@0.7.0: {} @@ -14847,7 +15001,14 @@ snapshots: rgbcolor@1.0.1: optional: true - 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: @@ -14880,13 +15041,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 - roughjs@4.6.6: - dependencies: - hachure-fill: 0.5.2 - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - router@2.2.0: dependencies: debug: 4.4.3 @@ -14899,12 +15053,12 @@ snapshots: rrweb-cssom@0.8.0: {} + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - rw@1.3.3: {} - rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -15102,6 +15256,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 +15337,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 +15351,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 +15407,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 +15438,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 @@ -15278,8 +15466,6 @@ snapshots: stylis@4.2.0: {} - stylis@4.4.0: {} - suf-log@2.5.3: dependencies: s.color: 0.0.15 @@ -15375,10 +15561,10 @@ snapshots: tiny-inflate@1.0.3: {} - tiny-invariant@1.3.3: {} - tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.2: {} @@ -15400,6 +15586,8 @@ snapshots: dependencies: tldts-core: 6.1.75 + tmp@0.2.7: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -15428,8 +15616,6 @@ snapshots: dependencies: typescript: 5.6.3 - ts-dedent@2.3.0: {} - tsconfck@3.1.6(typescript@5.6.3): optionalDependencies: typescript: 5.6.3 @@ -15645,6 +15831,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 +15874,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 @@ -15719,23 +15918,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - victory-vendor@37.3.6: - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-timer: 3.0.1 - vite-bundle-analyzer@0.17.1: {} vite-node@3.0.4(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4): @@ -15759,6 +15941,25 @@ snapshots: - tsx - yaml + 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.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.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: + - '@types/node' + - rollup + - supports-color + 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) @@ -15778,6 +15979,22 @@ snapshots: - rollup - supports-color + 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.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: '@rollup/pluginutils': 5.3.0(rollup@4.57.1) @@ -16083,6 +16300,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 +16316,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 +16419,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 59adc226018441334c46fdccd83947893ca3d9a2 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 25 Jun 2026 12:20:49 +0200 Subject: [PATCH 02/42] 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 --- packages/tokens/lint-staged.config.mjs | 1 + packages/tokens/src/eject-tokens.ts | 29 +++++++++---------- packages/tokens/tsconfig.json | 7 ++++- packages/ui/lint-staged.config.mjs | 1 + .../button/regular-button/button.tsx | 6 ++++ packages/ui/src/components/modal/modal.tsx | 4 +-- .../item/segment-picker-item.tsx | 4 +-- .../segment-picker/segment-picker.tsx | 6 ++-- .../segment-picker/utils/context.ts | 4 +-- 9 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 packages/tokens/lint-staged.config.mjs create mode 100644 packages/ui/lint-staged.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/src/eject-tokens.ts b/packages/tokens/src/eject-tokens.ts index 34bb87793..65b259f14 100644 --- a/packages/tokens/src/eject-tokens.ts +++ b/packages/tokens/src/eject-tokens.ts @@ -6,24 +6,23 @@ import { TOKEN_OUTPUT_DIR } from './constants'; import { toFileName } from './to-file-name'; export function ejectTokens(): void { - try { - if (!existsSync(TOKEN_OUTPUT_DIR)) { - mkdirSync(TOKEN_OUTPUT_DIR, { recursive: true }); - } + // 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 }); + } - // Filter out tokens metadata to get themes - const themes = Object.entries(tokens).filter(([name]) => !name.startsWith('$')); + // Filter out tokens metadata to get themes + const themes = Object.entries(tokens).filter(([name]) => !name.startsWith('$')); - for (const [name, theme] of themes) { - const fileName = toFileName(name); + for (const [name, theme] of themes) { + const fileName = toFileName(name); - const outputFilePath = path.join(TOKEN_OUTPUT_DIR, `${fileName}.json`); - console.log(outputFilePath); + const outputFilePath = path.join(TOKEN_OUTPUT_DIR, `${fileName}.json`); + console.log(outputFilePath); - // Write the property value to a new JSON file - writeFileSync(outputFilePath, JSON.stringify(theme, null, 2), 'utf8'); - } - } catch (error) { - console.error('Error extracting JSON properties:', error); + // Write the property value to a new JSON file + writeFileSync(outputFilePath, JSON.stringify(theme, null, 2), 'utf8'); } } diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json index 9536a0f41..e9202c2d4 100644 --- a/packages/tokens/tsconfig.json +++ b/packages/tokens/tsconfig.json @@ -1,3 +1,8 @@ { - "extends": "../../tsconfig.base.json" + "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/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/src/components/button/regular-button/button.tsx b/packages/ui/src/components/button/regular-button/button.tsx index 30cfa21d6..3349834bf 100644 --- a/packages/ui/src/components/button/regular-button/button.tsx +++ b/packages/ui/src/components/button/regular-button/button.tsx @@ -60,6 +60,12 @@ const ButtonComponent = forwardRef{props.children}; } + // The overloads only accept string / icon / icon + label children, so this is + // unreachable for type-checked callers. It only fires for dynamic or `as any` + // children that TS can't catch - fail loudly instead of silently rendering nothing. + console.error( + 'Button: `children` did not match a supported variant (label, icon, or icon + label). Rendering nothing.', + ); return null; }, ); diff --git a/packages/ui/src/components/modal/modal.tsx b/packages/ui/src/components/modal/modal.tsx index 3c189da98..36298d9c9 100644 --- a/packages/ui/src/components/modal/modal.tsx +++ b/packages/ui/src/components/modal/modal.tsx @@ -77,7 +77,7 @@ export const Modal = forwardRef( -
+
{icon &&
{icon}
} @@ -99,7 +99,7 @@ export const Modal = forwardRef( )}
- {children &&
{children}
} + {children &&
{children}
} {footer &&
{footer}
}
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 index d5d6e03f5..1e0f42e5b 100644 --- a/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx +++ b/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx @@ -8,7 +8,7 @@ import { NavIconButtonProps } from '@ui/components/button/nav-button/nav-icon-bu 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 { MouseEventHandler, useContext } from 'react'; +import { MouseEvent, useContext } from 'react'; import itemShapeStyles from './segment-picker-item-shape.module.css'; @@ -45,7 +45,7 @@ export function Item({ children, value, ...buttonProps }: SegmentPickerItemProps const props = { className: clsx(itemShapeStyles['item'], itemShapeStyles[shape || '']), isSelected: selectedValue === value, - onClick: (event: MouseEventHandler) => onSelect(event, value), + onClick: (event: MouseEvent) => onSelect(event, value), shape, children, ...other, diff --git a/packages/ui/src/components/segment-picker/segment-picker.tsx b/packages/ui/src/components/segment-picker/segment-picker.tsx index 122b89c0a..560ee189b 100644 --- a/packages/ui/src/components/segment-picker/segment-picker.tsx +++ b/packages/ui/src/components/segment-picker/segment-picker.tsx @@ -1,7 +1,7 @@ import { Shape } from '@ui/components/button/types'; import { Size } from '@ui/shared/types/size'; import clsx from 'clsx'; -import { ForwardRefExoticComponent, MouseEventHandler, ReactElement, forwardRef, useState } from 'react'; +import { ForwardRefExoticComponent, MouseEvent, ReactElement, forwardRef, useState } from 'react'; import borderRadiusStyles from './border-radius-size.module.css'; import styles from './segment-picker.module.css'; @@ -21,7 +21,7 @@ type SegmentPickerPropsBase = { */ shape?: Shape; className?: string; - onChange?: (event: MouseEventHandler, value: string) => void; + onChange?: (event: MouseEvent, value: string) => void; }; type ControlledSegmentPickerProps = { @@ -52,7 +52,7 @@ export const SegmentPicker = forwardRef( const selectedValue = isControlled ? value : internalValue; - const handleSelect = (event: React.MouseEventHandler, newValue: string) => { + const handleSelect = (event: MouseEvent, newValue: string) => { if (!isControlled) { setInternalValue(newValue); } diff --git a/packages/ui/src/components/segment-picker/utils/context.ts b/packages/ui/src/components/segment-picker/utils/context.ts index 041498703..0a2a33938 100644 --- a/packages/ui/src/components/segment-picker/utils/context.ts +++ b/packages/ui/src/components/segment-picker/utils/context.ts @@ -1,10 +1,10 @@ import { Shape } from '@ui/components/button/types'; import { Size } from '@ui/shared/types/size'; -import { MouseEventHandler, createContext } from 'react'; +import { MouseEvent, createContext } from 'react'; type SegmentPickerContextType = { selectedValue: string | undefined; - onSelect: (event: MouseEventHandler, value: string) => void; + onSelect: (event: MouseEvent, value: string) => void; size?: Size; shape?: Shape; }; From 7e8475cf51c31348fdcc6db3af514583de3602ed Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 25 Jun 2026 12:29:35 +0200 Subject: [PATCH 03/42] 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 --- packages/tokens/output.json | 0 packages/tokens/src/constants.ts | 2 +- packages/tokens/src/generate-css-bundle.ts | 5 ++-- packages/ui/combine-css-bundle.mts | 5 +++- packages/ui/scripts/check-built-css.ts | 18 +++++++------ .../nav-icon-button/nav-icon-button.tsx | 2 +- .../icon-button/icon-button.tsx | 2 +- packages/ui/src/components/button/types.ts | 2 +- .../components/node/node-panel/node-panel.tsx | 8 +++--- .../item/segment-picker-item.tsx | 2 +- .../segment-picker/segment-picker.tsx | 2 +- .../segment-picker/utils/get-valid-shape.ts | 2 +- .../ui/src/components/snackbar/snackbar.tsx | 4 ++- packages/ui/src/components/switch/switch.tsx | 7 +---- .../components/tooltip/tooltip-trigger.tsx | 8 +++--- .../ui/src/components/tooltip/tooltip.tsx | 26 +++---------------- packages/ui/src/index.ts | 2 -- packages/ui/vite.config.mts | 18 +++++++------ 18 files changed, 49 insertions(+), 66 deletions(-) delete mode 100644 packages/tokens/output.json diff --git a/packages/tokens/output.json b/packages/tokens/output.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/tokens/src/constants.ts b/packages/tokens/src/constants.ts index 8b31425b4..3f73bf00f 100644 --- a/packages/tokens/src/constants.ts +++ b/packages/tokens/src/constants.ts @@ -1,2 +1,2 @@ export const OUTPUT_DIR = './dist/'; -export const TOKEN_OUTPUT_DIR = `${OUTPUT_DIR}/tokens/`; +export const TOKEN_OUTPUT_DIR = `${OUTPUT_DIR}tokens/`; diff --git a/packages/tokens/src/generate-css-bundle.ts b/packages/tokens/src/generate-css-bundle.ts index 49f9c1f85..45dfe2400 100644 --- a/packages/tokens/src/generate-css-bundle.ts +++ b/packages/tokens/src/generate-css-bundle.ts @@ -5,9 +5,10 @@ import { Theme } from './types'; const { primitives, themes } = config; -const codeChunks: string[] = []; - export async function generateCSSBundle() { + // Local to the call so repeated invocations in one process don't accumulate. + const codeChunks: string[] = []; + for (const primitive of primitives) { codeChunks.push(createPrimitiveImport(primitive)); } diff --git a/packages/ui/combine-css-bundle.mts b/packages/ui/combine-css-bundle.mts index ea8a3ba1b..2461ca66a 100644 --- a/packages/ui/combine-css-bundle.mts +++ b/packages/ui/combine-css-bundle.mts @@ -16,7 +16,7 @@ export function combineCssBundle(rootDirectory: string): Plugin { const stylesDirectory = path.resolve(rootDirectory, 'src/styles'); return { - name: 'overflow-ui:combine-css-bundle', + name: 'wb-ui:combine-css-bundle', apply: 'build', closeBundle() { writeCombinedStylesheet(distributionDirectory, stylesDirectory); @@ -33,6 +33,9 @@ function writeCombinedStylesheet(distributionDirectory: string, stylesDirectory: const assetsDirectory = path.resolve(distributionDirectory, 'assets'); if (!fs.existsSync(assetsDirectory)) return; + // Alphabetical order. Within one cascade layer file order only decides ties + // between equal-specificity rules; components don't share selectors, so this + // is safe. Replace .sort() with an explicit order if that ever changes. const styles = fs .readdirSync(assetsDirectory) .filter((file) => file.endsWith('.css')) diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts index 802f51568..641fd7f53 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -1,15 +1,17 @@ /** * Guard against the WB-222 bug class in built CSS output. * - * Mirrors the `local/no-invalid-var` stylelint rule but runs after `vite - * build`, so the release fails even if the source-level check is bypassed - * (disabled rule, --no-verify commit, a build-tool transform that - * regresses, etc.). The rule: `var()`'s first argument must be a - * `` (a dashed-ident) — never another function. - * Browsers silently invalidate `var(var(--foo))` and fall back, which is - * how WB-222 shipped a wrong snackbar icon color. + * Runs after `vite build` so a regression fails the release even if it slips + * past review or a build-tool transform introduces it. The rule: `var()`'s + * first argument must be a `` (a dashed-ident) - never + * another function. Browsers silently invalidate `var(var(--foo))` and fall + * back, which is how WB-222 shipped a wrong snackbar icon color. * - * Run after `vite build`. Exits non-zero on any match. + * This is the only line of defense for this bug class today; a source-level + * lint rule (e.g. a stylelint `no-invalid-var`) would catch it earlier but + * none is configured yet. + * + * Exits non-zero on any match. */ import { globSync, readFileSync } from 'node:fs'; import path from 'node:path'; 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 index 224c832e8..e0dffbca3 100644 --- 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 @@ -20,7 +20,7 @@ export type NavIconButtonProps = { } & NavBaseButtonProps; export const NavIconButton = forwardRef( - ({ size = 'medium', shape = '', children, transparent, ...props }, ref) => ( + ({ size = 'medium', shape = 'default', children, transparent, ...props }, ref) => ( ( - ({ size = 'medium', shape = '', children, ...props }, ref) => ( + ({ size = 'medium', shape = 'default', children, ...props }, ref) => ( ) { 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 index 1e0f42e5b..0cabf15d1 100644 --- a/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx +++ b/packages/ui/src/components/segment-picker/item/segment-picker-item.tsx @@ -43,7 +43,7 @@ export function Item({ children, value, ...buttonProps }: SegmentPickerItemProps const { selectedValue, onSelect, shape, ...other } = context; const props = { - className: clsx(itemShapeStyles['item'], itemShapeStyles[shape || '']), + className: clsx(itemShapeStyles['item'], itemShapeStyles[shape ?? 'default']), isSelected: selectedValue === value, onClick: (event: MouseEvent) => onSelect(event, value), shape, diff --git a/packages/ui/src/components/segment-picker/segment-picker.tsx b/packages/ui/src/components/segment-picker/segment-picker.tsx index 560ee189b..34444346e 100644 --- a/packages/ui/src/components/segment-picker/segment-picker.tsx +++ b/packages/ui/src/components/segment-picker/segment-picker.tsx @@ -45,7 +45,7 @@ type SegmentPickerComponent = ForwardRefExoticComponent( - ({ children, value, defaultValue, size = 'medium', shape = '', className, onChange }, ref) => { + ({ children, value, defaultValue, size = 'medium', shape = 'default', className, onChange }, ref) => { const validShape = getValidShape(shape, children); const isControlled = value !== undefined; const [internalValue, setInternalValue] = useState(defaultValue); 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 index c97903da9..d70248787 100644 --- a/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts +++ b/packages/ui/src/components/segment-picker/utils/get-valid-shape.ts @@ -15,7 +15,7 @@ export function getValidShape(shape: Shape, items: ReactElement( ({ variant, title, subtitle, buttonLabel, onButtonClick, close = false, onClose }, ref) => ( diff --git a/packages/ui/src/components/switch/switch.tsx b/packages/ui/src/components/switch/switch.tsx index 31c4d9a42..a23c9ac32 100644 --- a/packages/ui/src/components/switch/switch.tsx +++ b/packages/ui/src/components/switch/switch.tsx @@ -14,10 +14,6 @@ export type BaseSwitchProps = { * Size of the switch component */ size?: SelectorSize; - /** - * Custom styles to apply to the switch - */ - styles?: string; /** * Custom content for the thumb of the switch */ @@ -51,7 +47,6 @@ export type BaseSwitchProps = { export function Switch({ size = 'medium', className, - styles, thumbChildren, trackChildren, onChange, @@ -64,7 +59,7 @@ export function Switch({ return ( } {...props} diff --git a/packages/ui/src/components/tooltip/tooltip-trigger.tsx b/packages/ui/src/components/tooltip/tooltip-trigger.tsx index 8c9515876..b36184c7c 100644 --- a/packages/ui/src/components/tooltip/tooltip-trigger.tsx +++ b/packages/ui/src/components/tooltip/tooltip-trigger.tsx @@ -2,7 +2,7 @@ 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 { useTooltipDelay } from './tooltip'; +import { TOOLTIP_CLOSE_DELAY, TOOLTIP_OPEN_DELAY } from './tooltip'; /** * Tooltips trigger is the the element that toggles the tooltip @@ -16,13 +16,11 @@ export const TooltipTrigger = forwardRef< asChild?: boolean; } >(function TooltipTrigger({ children, asChild = false, ...props }, propertyRef) { - const { delay, closeDelay } = useTooltipDelay(); - return ( } - delay={delay} - closeDelay={closeDelay} + delay={TOOLTIP_OPEN_DELAY} + closeDelay={TOOLTIP_CLOSE_DELAY} // Keep parity with the previous Floating UI behaviour: clicking the // trigger should not dismiss the tooltip while hover is still active. closeOnClick={false} diff --git a/packages/ui/src/components/tooltip/tooltip.tsx b/packages/ui/src/components/tooltip/tooltip.tsx index 8b8edad36..0aadfa5ff 100644 --- a/packages/ui/src/components/tooltip/tooltip.tsx +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -4,8 +4,8 @@ import { ReactNode, createContext, useContext, useMemo } from 'react'; import { TooltipContent } from './tooltip-content'; import { TooltipTrigger } from './tooltip-trigger'; -const TOOLTIP_OPEN_DELAY = 500; -const TOOLTIP_CLOSE_DELAY = 0; +export const TOOLTIP_OPEN_DELAY = 500; +export const TOOLTIP_CLOSE_DELAY = 0; type Side = 'top' | 'right' | 'bottom' | 'left'; type Align = 'start' | 'end'; @@ -85,31 +85,11 @@ export function Tooltip({ children, initialOpen, placement = 'bottom', open, onO : undefined } > - {children} + {children} ); } -const TooltipDelayContext = createContext<{ - delay: number; - closeDelay: number; -}>({ - delay: TOOLTIP_OPEN_DELAY, - closeDelay: TOOLTIP_CLOSE_DELAY, -}); - -export function useTooltipDelay() { - return useContext(TooltipDelayContext); -} - -function TooltipDelayApplier({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} - Tooltip.Content = TooltipContent; Tooltip.Trigger = TooltipTrigger; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 148b0cf48..096a5a7ad 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -1,5 +1,3 @@ -// [TODO] Improvement: Should we automate it? -// Using glob library could help us generate this entrypoint and imports import './styles/globals.css'; import './styles/layers.css'; import './styles/typography.css'; diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index 8def392e4..8cec3ca77 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -1,12 +1,14 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { visualizer } from 'rollup-plugin-visualizer'; import { defineConfig } from 'vite'; -import { libInjectCss } from 'vite-plugin-lib-inject-css'; -import { viteStaticCopy } from 'vite-plugin-static-copy'; import dts from 'vite-plugin-dts'; -import { visualizer } from 'rollup-plugin-visualizer'; -import { boxSizingPlugin } from './postcss-box-sizing.mts'; +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'; const rootDirectory = path.dirname(fileURLToPath(import.meta.url)); @@ -38,10 +40,10 @@ const externalModules = new Set(['react', 'react-dom', 'react/jsx-runtime']); function getEntries(): Record { const entries: Record = { - index: path.resolve(rootDirectory,'src/index.ts'), + index: path.resolve(rootDirectory, 'src/index.ts'), }; for (const name of componentEntries) { - entries[name] = path.resolve(rootDirectory,`src/components/${name}/index.ts`); + entries[name] = path.resolve(rootDirectory, `src/components/${name}/index.ts`); } return entries; } @@ -82,7 +84,7 @@ export default defineConfig({ build: { lib: { entry: getEntries(), - name: 'Overflow UI', + name: '@workflowbuilder/ui', formats: ['es'], }, rollupOptions: { @@ -111,7 +113,7 @@ export default defineConfig({ }, resolve: { alias: { - '@ui': path.resolve(rootDirectory,'./src'), + '@ui': path.resolve(rootDirectory, './src'), }, }, plugins: [ From a80d4215644db6ec9794a7d62303fe71904e6b30 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 25 Jun 2026 12:48:34 +0200 Subject: [PATCH 04/42] 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. --- package.json | 2 +- packages/tokens/package.json | 6 +- packages/tokens/src/to-file-name.spec.ts | 21 +++++ packages/tokens/vitest.config.mts | 7 ++ packages/ui/package.json | 4 +- .../ui/src/components/button/guards.spec.ts | 85 +++++++++++++++++++ .../components/date-picker/date-picker.tsx | 50 +---------- .../components/date-picker/date-utils.spec.ts | 83 ++++++++++++++++++ .../src/components/date-picker/date-utils.ts | 50 +++++++++++ packages/ui/src/components/menu/menu.tsx | 40 +-------- .../ui/src/components/menu/placement.spec.ts | 44 ++++++++++ packages/ui/src/components/menu/placement.ts | 39 +++++++++ .../utils/get-valid-shape.spec.ts | 40 +++++++++ .../src/components/tooltip/placement.spec.ts | 15 ++++ .../ui/src/components/tooltip/placement.ts | 17 ++++ .../ui/src/components/tooltip/tooltip.tsx | 19 +---- packages/ui/src/shared/utils/arrays.spec.ts | 26 ++++++ packages/ui/tsconfig.json | 1 + packages/ui/vite.config.mts | 2 +- packages/ui/vitest.config.mts | 18 ++++ pnpm-lock.yaml | 10 ++- 21 files changed, 469 insertions(+), 110 deletions(-) create mode 100644 packages/tokens/src/to-file-name.spec.ts create mode 100644 packages/tokens/vitest.config.mts create mode 100644 packages/ui/src/components/button/guards.spec.ts 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/menu/placement.spec.ts create mode 100644 packages/ui/src/components/menu/placement.ts create mode 100644 packages/ui/src/components/segment-picker/utils/get-valid-shape.spec.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/shared/utils/arrays.spec.ts create mode 100644 packages/ui/vitest.config.mts diff --git a/package.json b/package.json index 5a08d16ae..92a18d43d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "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/tokens/package.json b/packages/tokens/package.json index 6fbd5791a..112c03553 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -8,12 +8,14 @@ ], "scripts": { "build": "tsx src/index.ts", - "prepare": "tsx src/index.ts" + "prepare": "tsx src/index.ts", + "test": "vitest run" }, "devDependencies": { "@tokens-studio/sd-transforms": "^1.2.12", "remeda": "^2.21.2", "style-dictionary": "^4.3.3", - "tsx": "^4.19.3" + "tsx": "^4.19.3", + "vitest": "^3.0.4" } } 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/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/package.json b/packages/ui/package.json index f1533b2aa..088e83e8c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -34,6 +34,7 @@ "lint": "eslint", "lint:fix": "eslint --fix", "typecheck": "tsc --noEmit --pretty", + "test": "vitest run", "build:stats": "BUNDLE_STATS=1 vite build" }, "module": "./dist/index.js", @@ -81,7 +82,8 @@ "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" + "vite-plugin-static-copy": "^2.3.1", + "vitest": "^3.0.4" }, "peerDependencies": { "@base-ui/react": "^1.4.0", 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/date-picker/date-picker.tsx b/packages/ui/src/components/date-picker/date-picker.tsx index 2d88a620d..da6672c0f 100644 --- a/packages/ui/src/components/date-picker/date-picker.tsx +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -5,6 +5,7 @@ import { format } from 'date-fns'; import { forwardRef, useCallback, useMemo, useState } from 'react'; import { type DateRange, DayPicker, type Matcher } from 'react-day-picker'; +import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; import styles from './date-picker.module.css'; import './variables.css'; import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; @@ -274,14 +275,6 @@ function CalendarChevron({ return ; } -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. -} - function formatTriggerLabel( value: Date | [Date, Date] | Date[] | null, type: DatePickerType, @@ -303,48 +296,7 @@ function formatTriggerLabel( return null; } -function isDateTuple(value: unknown): value is [Date, Date] { - return Array.isArray(value) && value.length === 2 && value[0] instanceof Date && value[1] instanceof Date; -} - function toDateRange(value: Date | [Date, Date] | Date[] | null): DateRange | undefined { if (!isDateTuple(value)) return undefined; return { from: value[0], to: value[1] }; } - -const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/; - -/** - * 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. - */ -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; -} - -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/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/menu/menu.tsx b/packages/ui/src/components/menu/menu.tsx index 1a0cffcb1..213061c1e 100644 --- a/packages/ui/src/components/menu/menu.tsx +++ b/packages/ui/src/components/menu/menu.tsx @@ -2,25 +2,15 @@ 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 { type ComponentProps, ReactElement, memo } from 'react'; +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'; -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; +export type { OffsetOptions, Placement } from './placement'; type MenuProps = { /** @@ -67,30 +57,6 @@ type MenuProps = { children?: ReactElement; }; -type PositionerProps = ComponentProps; -type PositionerSide = NonNullable; -type PositionerAlign = NonNullable; - -function placementToSideAlign(placement: Placement): { - side: PositionerSide; - align: PositionerAlign; -} { - const [side, alignRaw] = placement.split('-') as [PositionerSide, PositionerAlign | undefined]; - return { side, align: alignRaw ?? 'center' }; -} - -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 }; -} - export const Menu = memo( ({ items, size = 'medium', placement = 'bottom-end', children, open, offset, onOpenChange }: MenuProps) => { const { side, align } = placementToSideAlign(placement); 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/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/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.tsx b/packages/ui/src/components/tooltip/tooltip.tsx index 0aadfa5ff..35237839a 100644 --- a/packages/ui/src/components/tooltip/tooltip.tsx +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -1,16 +1,14 @@ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; import { ReactNode, createContext, useContext, useMemo } from 'react'; +import { type PlacementContextValue, type TooltipPlacement, placementToSideAlign } from './placement'; import { TooltipContent } from './tooltip-content'; import { TooltipTrigger } from './tooltip-trigger'; export const TOOLTIP_OPEN_DELAY = 500; export const TOOLTIP_CLOSE_DELAY = 0; -type Side = 'top' | 'right' | 'bottom' | 'left'; -type Align = 'start' | 'end'; - -export type TooltipPlacement = Side | `${Side}-${Align}`; +export type { TooltipPlacement } from './placement'; export type TooltipOptions = { /** @@ -31,11 +29,6 @@ export type TooltipOptions = { onOpenChange?: (open: boolean) => void; }; -type PlacementContextValue = { - side: Side; - align: 'start' | 'center' | 'end'; -}; - const TooltipPlacementContext = createContext({ side: 'bottom', align: 'center', @@ -45,14 +38,6 @@ export function useTooltipPlacement(): PlacementContextValue { return useContext(TooltipPlacementContext); } -function placementToSideAlign(placement: TooltipPlacement): PlacementContextValue { - const [side, align] = placement.split('-') as [ - PlacementContextValue['side'], - PlacementContextValue['align'] | undefined, - ]; - return { side, align: align ?? 'center' }; -} - type Props = { /** * Tooltip reference element. 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/tsconfig.json b/packages/ui/tsconfig.json index 3ee5e816d..9660df184 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "baseUrl": ".", "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vitest/globals"], "paths": { "@ui/*": ["./src/*"] } diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index 8cec3ca77..d4a170347 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -120,7 +120,7 @@ export default defineConfig({ libInjectCss(), // Per-entry .d.ts; rollupTypes is intentionally off (incompatible with this // multi-entry setup, see vite-plugin-dts docs). - dts({ entryRoot: 'src' }), + dts({ entryRoot: 'src', exclude: ['src/**/*.spec.{ts,tsx}'] }), 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 8fe65011b..852f56017 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -547,6 +547,9 @@ importers: 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: {} @@ -616,6 +619,9 @@ importers: 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: @@ -8703,7 +8709,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 @@ -10239,7 +10245,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 From 135036c7cd09c12eeb7cb7041e0f05799c1ad019 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 25 Jun 2026 12:55:52 +0200 Subject: [PATCH 05/42] 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) --- CLAUDE.md | 2 +- packages/ui/README.md | 46 +++++++++++++++++++++++++--------------- packages/ui/package.json | 23 ++++++++++---------- pnpm-lock.yaml | 24 ++++++++++----------- 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 96550df58..49eb01712 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,7 +137,7 @@ If you're new to this repo and want to build your own consumer app or POC, follo ### Releasing `@workflowbuilder/sdk` -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 (`@workflowbuilder/ui-tokens` is private too) and listed under `ignore` in `.changeset/config.json`. Because there are now two publishable packages, the release tag scheme below (single-package `v*`) still needs migrating to scoped tags (`@workflowbuilder/sdk@X.Y.Z`) before publishing `@workflowbuilder/ui` from this repo - see § "Tag format" and `packages/sdk/RELEASE.md`. +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`). Because there are now two publishable packages, the release tag scheme below (single-package `v*`) still needs migrating to scoped tags (`@workflowbuilder/sdk@X.Y.Z`) before publishing `@workflowbuilder/ui` from this repo - see § "Tag format" and `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. diff --git a/packages/ui/README.md b/packages/ui/README.md index 5c08eb484..c03d6e762 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -1,6 +1,6 @@ # @workflowbuilder/ui -A React library for creating node-based user interfaces and diagram-driven apps. Built to work seamlessly with React Flow, it provides a collection of ready-to-use components and templates that simplify the development of visual editors, workflows, and interactive diagrams. +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/)**. @@ -8,7 +8,7 @@ Developed and maintained by **[Synergy Codes](https://www.synergycodes.com/)**. ### 📦 Installation -Use one of the commands below to add **Overflow UI** to your project: +Use one of the commands below to add **`@workflowbuilder/ui`** to your project: ```bash npm install @workflowbuilder/ui @@ -22,6 +22,13 @@ pnpm add @workflowbuilder/ui yarn add @workflowbuilder/ui ``` +> **Peer dependencies.** Install `@base-ui/react` (range `~1.4.0`) alongside +> `react` / `react-dom`. `@base-ui/react` is pinned to the `1.4.x` line because +> later versions regressed dialog/menu/tooltip transitions. Everything else the +> components use (date-fns, react-day-picker, clsx, Phosphor icons) is bundled +> into the package; only `react-textarea-autosize` is pulled in as a regular +> dependency. + ### 🎨 Import styles Import components from the package root (the recommended default). Their styles, @@ -62,7 +69,7 @@ import { Input } from '@workflowbuilder/ui'; ## Overview -Forget cobbling together UI kits with diagram libraries. Overflow UI provides a unified set of designed, ready-to-use components: buttons, inputs, accordions, node templates, and more — all built to work seamlessly together. +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 @@ -74,7 +81,7 @@ Forget cobbling together UI kits with diagram libraries. Overflow UI provides a ## Customization -Each Overflow UI component uses CSS variables that are derived from primitive values. +Each `@workflowbuilder/ui` component uses CSS variables that are derived from primitive values. You can override them: @@ -92,9 +99,9 @@ or a derived value used by the selected component: } ``` -### Overflow UI css layers +### `@workflowbuilder/ui` css layers -Overflow 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 Overflow UI defines, so your styles will always win the specificity war. You can customize Overflow UI components with simple `input {}`. +`@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 { @@ -104,7 +111,7 @@ Overflow UI uses [CSS layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@ } ``` -Default Overflow UI order: +Default `@workflowbuilder/ui` order: ```css @layer ui.base, ui.component; @@ -112,34 +119,39 @@ Default Overflow UI order: # 🛠️ Development -To develop and test components, follow these steps in monorepo root: +Run these from the monorepo root. -1. Install dependencies: +Install dependencies: ```bash pnpm i ``` -2. Build UI: +Build the library (runs the built-CSS guard afterwards): + +```bash +pnpm --filter @workflowbuilder/ui build +``` + +Rebuild on change while developing: ```bash -pnpm ui build +pnpm --filter @workflowbuilder/ui dev ``` -3. Run preview page: +Run the unit tests: ```bash -pnpm ui preview +pnpm --filter @workflowbuilder/ui test ``` -Now visit `localhost:3000` to see every change in components reflected in the preview page. -Edit `ui/preview-page/preview-page.tsx` to display desired components. +To see components rendered live, start the documentation site with `pnpm dev:docs` and open its UI Library gallery. ### 📣 Important Note on Underlying Technology -> **Overflow 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.** +> **`@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, Overflow UI provides components that are **accessible by default** and **fully customizable** through our design tokens. +> Thanks to Base UI, `@workflowbuilder/ui` provides components that are **accessible by default** and **fully customizable** through our design tokens. > > Earlier `1.0.0` betas were built on the now-deprecated [MUI Base](https://v6.mui.com/base-ui/getting-started/). From `1.0.0-beta.28` the library is built on Base UI; see [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. diff --git a/packages/ui/package.json b/packages/ui/package.json index 088e83e8c..7feb5b31b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,18 +2,17 @@ "name": "@workflowbuilder/ui", "type": "module", "version": "1.0.0-beta.28", - "description": "A React library for creating node-based UIs and diagram-driven applications. Perfect for React Flow users, providing ready-to-use node templates and components that work seamlessly with React Flow's ecosystem.", + "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", - "diagram", - "node-based", "components", + "component-library", + "base-ui", + "design-system", + "accessible", "typescript", - "react flow", - "flow", - "node-templates", - "diagram-components" + "workflow-builder" ], "author": "Synergy Codes", "license": "MIT", @@ -60,21 +59,21 @@ "./tokens.css": "./dist/tokens.css" }, "dependencies": { - "@phosphor-icons/react": "catalog:", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "react-day-picker": "^9.14.0", "react-textarea-autosize": "^8.5.6" }, "devDependencies": { "@base-ui/react": "catalog:", + "@phosphor-icons/react": "catalog:", "@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", @@ -86,7 +85,7 @@ "vitest": "^3.0.4" }, "peerDependencies": { - "@base-ui/react": "^1.4.0", + "@base-ui/react": "~1.4.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 852f56017..b52d2543e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -555,18 +555,6 @@ importers: packages/ui: dependencies: - '@phosphor-icons/react': - specifier: 'catalog:' - version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - clsx: - specifier: ^2.1.1 - version: 2.1.1 - date-fns: - specifier: ^4.1.0 - version: 4.1.0 - react-day-picker: - specifier: ^9.14.0 - version: 9.14.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) @@ -574,6 +562,9 @@ importers: '@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) '@types/react': specifier: 'catalog:' version: 19.1.8 @@ -583,6 +574,12 @@ importers: '@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)) @@ -595,6 +592,9 @@ importers: 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) From 7959366808062319b3ca03837c7dd17d2dbe5b06 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 25 Jun 2026 13:01:48 +0200 Subject: [PATCH 06/42] 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). --- .github/workflows/pr-check.yml | 43 ++++++++++- .github/workflows/release-sdk.yml | 18 +++-- .github/workflows/release-ui.yml | 114 ++++++++++++++++++++++++++++++ CLAUDE.md | 4 +- packages/sdk/RELEASE.md | 36 ++++++---- 5 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/release-ui.yml diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 3c5815076..02f659c3d 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,9 +1,9 @@ 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 +# is safe to merge. Focus on the published packages @workflowbuilder/sdk and +# @workflowbuilder/ui + 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: @@ -75,3 +75,40 @@ jobs: - name: Build run: pnpm --filter @workflowbuilder/sdk build:lib + + ui: + name: UI 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 + 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 480fd4118..66ce14afd 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: @@ -60,10 +62,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 49eb01712..962e0ac25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -137,7 +137,7 @@ If you're new to this repo and want to build your own consumer app or POC, follo ### Releasing `@workflowbuilder/sdk` -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`). Because there are now two publishable packages, the release tag scheme below (single-package `v*`) still needs migrating to scoped tags (`@workflowbuilder/sdk@X.Y.Z`) before publishing `@workflowbuilder/ui` from this repo - see § "Tag format" and `packages/sdk/RELEASE.md`. +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. @@ -162,7 +162,7 @@ Two workspaces are npm-published: `@workflowbuilder/sdk` and `@workflowbuilder/u 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 currently follows the ng-diagram convention (single-package monorepo, `v*` regex). A second publishable package (`@workflowbuilder/ui`) now exists, so this needs migrating to scoped tags (`@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/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). From 0b3cbccefe5c3b3194aefc3324f683192d001b7c Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 26 Jun 2026 12:31:09 +0200 Subject: [PATCH 07/42] 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. --- packages/ui/README.md | 13 +++++++------ packages/ui/package.json | 3 +-- pnpm-lock.yaml | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/ui/README.md b/packages/ui/README.md index c03d6e762..40fbe4dcd 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -22,12 +22,13 @@ pnpm add @workflowbuilder/ui yarn add @workflowbuilder/ui ``` -> **Peer dependencies.** Install `@base-ui/react` (range `~1.4.0`) alongside -> `react` / `react-dom`. `@base-ui/react` is pinned to the `1.4.x` line because -> later versions regressed dialog/menu/tooltip transitions. Everything else the -> components use (date-fns, react-day-picker, clsx, Phosphor icons) is bundled -> into the package; only `react-textarea-autosize` is pulled in as a regular -> dependency. +> **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 diff --git a/packages/ui/package.json b/packages/ui/package.json index 7feb5b31b..4778d5bec 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -59,10 +59,10 @@ "./tokens.css": "./dist/tokens.css" }, "dependencies": { + "@base-ui/react": "catalog:", "react-textarea-autosize": "^8.5.6" }, "devDependencies": { - "@base-ui/react": "catalog:", "@phosphor-icons/react": "catalog:", "@types/react": "catalog:", "@types/react-dom": "^19.1.0", @@ -85,7 +85,6 @@ "vitest": "^3.0.4" }, "peerDependencies": { - "@base-ui/react": "~1.4.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b52d2543e..950c7404f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -555,13 +555,13 @@ importers: 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) react-textarea-autosize: specifier: ^8.5.6 version: 8.5.9(@types/react@19.1.8)(react@19.1.0) devDependencies: - '@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) From ca743f916f8cb84d44695cfcbcbfd57bf54ad93d Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 26 Jun 2026 12:31:09 +0200 Subject: [PATCH 08/42] 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. --- packages/ui/src/components/date-picker/date-picker.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/date-picker/date-picker.tsx b/packages/ui/src/components/date-picker/date-picker.tsx index da6672c0f..97f0dffa6 100644 --- a/packages/ui/src/components/date-picker/date-picker.tsx +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -5,12 +5,15 @@ import { format } from 'date-fns'; import { forwardRef, useCallback, useMemo, useState } from 'react'; import { type DateRange, DayPicker, type Matcher } from 'react-day-picker'; -import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; import styles from './date-picker.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'; +// react-day-picker ships no styles of its own in v9 - this provides the calendar +// grid, month caption, and nav layout. Without it the calendar renders unstyled. +import 'react-day-picker/style.css'; +import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; import type { DatePickerProps, DatePickerType } from './types'; type Props = DatePickerProps & { From 62f3a12f01567fee4459f3974032301b82ee9880 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 26 Jun 2026 13:09:42 +0200 Subject: [PATCH 09/42] 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. --- .../date-picker/date-picker.module.css | 29 +++++++++++++++++++ .../src/components/date-picker/variables.css | 3 ++ 2 files changed, 32 insertions(+) diff --git a/packages/ui/src/components/date-picker/date-picker.module.css b/packages/ui/src/components/date-picker/date-picker.module.css index 236fe2db0..5e59a4296 100644 --- a/packages/ui/src/components/date-picker/date-picker.module.css +++ b/packages/ui/src/components/date-picker/date-picker.module.css @@ -35,3 +35,32 @@ color: var(--ax-public-date-picker-color); } } + +/* + * react-day-picker theming. These rules are intentionally UNLAYERED and scoped + * under `.calendar` so they win over react-day-picker's own unlayered + * style.css by specificity, regardless of the order the two stylesheets load + * (the docs bundle concatenates component CSS alphabetically, so import order + * is not reliable). `.calendar` is the Popover popup that wraps the calendar. + */ +.calendar :global(.rdp-root) { + --rdp-day_button-border-radius: 0.375rem; + /* drop the default accent ring on selected days - we fill instead */ + --rdp-selected-border: none; + --rdp-today-color: var(--ax-public-date-picker-date-today-color); +} + +.calendar :global(.rdp-day_button) { + transition: background-color 0.12s ease; +} + +/* react-day-picker ships no hover state of its own */ +.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); +} + +/* selected day: filled background instead of just an accent ring */ +.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/variables.css b/packages/ui/src/components/date-picker/variables.css index 85df37d61..4233cf50c 100644 --- a/packages/ui/src/components/date-picker/variables.css +++ b/packages/ui/src/components/date-picker/variables.css @@ -7,5 +7,8 @@ --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-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); } From a887eb1600d19d8915b83a05f0b0ce724d91cf25 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Fri, 26 Jun 2026 13:23:35 +0200 Subject: [PATCH 10/42] 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. --- .../src/components/date-picker/date-picker.module.css | 10 ++++++++++ packages/ui/src/components/date-picker/variables.css | 1 + 2 files changed, 11 insertions(+) diff --git a/packages/ui/src/components/date-picker/date-picker.module.css b/packages/ui/src/components/date-picker/date-picker.module.css index 5e59a4296..8c14abbd0 100644 --- a/packages/ui/src/components/date-picker/date-picker.module.css +++ b/packages/ui/src/components/date-picker/date-picker.module.css @@ -48,12 +48,22 @@ /* drop the default accent ring on selected days - we fill instead */ --rdp-selected-border: none; --rdp-today-color: var(--ax-public-date-picker-date-today-color); + /* react-day-picker's default accent is a raw blue; retheme it (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); +} + /* react-day-picker ships no hover state of its own */ .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); diff --git a/packages/ui/src/components/date-picker/variables.css b/packages/ui/src/components/date-picker/variables.css index 4233cf50c..db3004c4b 100644 --- a/packages/ui/src/components/date-picker/variables.css +++ b/packages/ui/src/components/date-picker/variables.css @@ -6,6 +6,7 @@ --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); From dd096cbc19c1805751b71fd252485e2e78869890 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Tue, 30 Jun 2026 11:58:59 +0200 Subject: [PATCH 11/42] 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. --- packages/ui/src/components/accordion/accordion.tsx | 8 ++++++-- .../switch/icon-switch/icon-switch.module.css | 14 ++++++++++++++ .../components/switch/icon-switch/icon-switch.tsx | 10 ++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/accordion/accordion.tsx b/packages/ui/src/components/accordion/accordion.tsx index 097ee827f..f4ab0a44e 100644 --- a/packages/ui/src/components/accordion/accordion.tsx +++ b/packages/ui/src/components/accordion/accordion.tsx @@ -55,14 +55,18 @@ export const Accordion = forwardRef( } return ( - + // The whole header is the click target (onClick below) and the arrow + // button bubbles into it. Collapsible is display-only here (controlled via + // `isExpanded`), so it must NOT also fire onToggle - otherwise clicking + // the arrow would run onExpandCollapse twice (button toggle + header bubble). +
{label} 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 index 7996bb1a6..a45808451 100644 --- a/packages/ui/src/components/switch/icon-switch/icon-switch.module.css +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.module.css @@ -89,4 +89,18 @@ .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 index 15ec21ba3..0e7ad1fcb 100644 --- a/packages/ui/src/components/switch/icon-switch/icon-switch.tsx +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx @@ -35,10 +35,12 @@ export function IconSwitch({ } thumbChildren={
-
- {!checked && icon} - {checked && IconChecked} -
+ {/* 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} From 11c56c560bfea06c8f712b750107dbc986642c50 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Tue, 30 Jun 2026 11:59:10 +0200 Subject: [PATCH 12/42] 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). --- packages/ui/CHANGELOG.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index c2a89e4ca..ecf3a9f96 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -17,11 +17,12 @@ upgrading. - Removed `@mui/material`, `@mui/base`, `@mantine/core`, `@mantine/dates`, `@emotion/*`, and `@floating-ui/react`. -- `@base-ui/react` (`^1.4.0`) is now a **peer dependency** - consumers must - install it alongside this package. +- `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 that install automatically. - `DatePicker` is rebuilt on `react-day-picker` + `date-fns`; `TextArea` on - `react-textarea-autosize`. `date-fns`, `react-day-picker`, and `clsx` are - bundled into the package output. + `react-textarea-autosize`. `date-fns`, `react-day-picker`, `clsx`, and the + Phosphor icons are bundled into the package output. #### Packaging @@ -50,7 +51,12 @@ upgrading. - **Switch**: `onChange` is `(checked: boolean, event: Event)`. The second argument is the native DOM event (previously typed as a React `ChangeEvent`, which never matched the value passed at - runtime). + runtime). The redundant `styles` prop was removed - use `className`. +- **SegmentPicker**: `onChange` is `(event: React.MouseEvent, value: string)`. +- **Shape** (the `shape` prop on `Button` and `SegmentPicker`): the type is now + `'default' | 'circle'` (was `'' | 'circle'`) - pass `'default'` instead of `''`. +- **Modal**: `className` and any forwarded HTML attributes now apply to the same + root element (previously split across the outer and content elements). - Transitions moved to the popup element, where Base UI sets `data-starting-style` / `data-ending-style`. @@ -60,3 +66,15 @@ upgrading. order, reset, typography) for per-component / subpath consumers. - `Input` gains a typed `error` prop wired to the error state. - `Snackbar` exposes proper `role="status"` / `aria-live` semantics. + +### Fixed + +- **DatePicker**: the calendar now ships `react-day-picker`'s stylesheet (it + rendered unstyled before); selection, today, and day-hover states are themed + to the design tokens, and the month-nav chevrons use a neutral color. +- **Accordion**: `onToggleOpen` no longer fires twice when the chevron is + clicked. +- **IconSwitch**: the thumb icon now swaps in uncontrolled mode (it was stuck + on the unchecked icon). +- **Button**: children that match no variant log a clear error instead of + silently rendering nothing. From d431a148cd87616b25ad3a40a69a767767ce4f66 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Tue, 30 Jun 2026 12:33:51 +0200 Subject: [PATCH 13/42] 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. --- packages/ui/CHANGELOG.md | 135 +++++++++++++++++---------------------- packages/ui/README.md | 2 +- packages/ui/package.json | 2 +- 3 files changed, 61 insertions(+), 78 deletions(-) diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index ecf3a9f96..adbe2fecf 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,80 +1,63 @@ # Changelog All notable changes to `@workflowbuilder/ui` are documented in this file. - -## 1.0.0-beta.28 - -This beta moves the library into the Workflow Builder monorepo and publishes it -as `@workflowbuilder/ui` (previously `@synergycodes/overflow-ui`). It rebuilds -the library on [Base UI](https://base-ui.com/) and removes the previous MUI / -Mantine / Emotion / Floating UI stack. It contains breaking changes relative to -`@synergycodes/overflow-ui@1.0.0-beta.27`; read the sections below before -upgrading. - -### Breaking changes - -#### Dependencies - -- Removed `@mui/material`, `@mui/base`, `@mantine/core`, `@mantine/dates`, - `@emotion/*`, and `@floating-ui/react`. -- `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 that install automatically. -- `DatePicker` is rebuilt on `react-day-picker` + `date-fns`; `TextArea` on - `react-textarea-autosize`. `date-fns`, `react-day-picker`, `clsx`, and the - Phosphor icons are bundled into the package output. - -#### Packaging - -- The build moved from a single bundle to a **multi-entry build** with a - per-component subpath export: `@workflowbuilder/ui/` (e.g. - `@workflowbuilder/ui/date-picker`). -- Importing from the package root still injects all required styles. Importing - per-component entries injects only that component's CSS, so add +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 (add `@workflowbuilder/ui/styles.css` once for the global layer order, reset, and - typography. - -#### Component API - -- **DatePicker**: the prop surface no longer forwards the full Mantine prop set. - It now accepts a curated list: `value`, `defaultValue`, `type`, - `valueFormat`, `placeholder`, `error`, `size`, `disabled`, `minDate`, - `maxDate`, `onChange`, `id`, `className`, `aria-label`, `aria-labelledby`. - - `valueFormat` uses `date-fns` tokens (e.g. `dd/MM/yyyy`). The legacy - `DD/MM/YYYY` (dayjs) default is accepted and converted for compatibility. - - In `range` mode, `onChange` fires `null` while a range is mid-selection and - emits the completed `[from, to]` tuple once both ends are picked. -- **Menu**: `onOpenChange` signature is now `(open: boolean, event?: Event)`. - The MUI `slotProps` / passthrough surface is no longer available. -- **Select**: `onChange` signature is `(event, value)` - the event is the first - argument, the selected value the second. -- **Switch**: `onChange` is `(checked: boolean, event: Event)`. The second - argument is the native DOM event (previously typed as a React - `ChangeEvent`, which never matched the value passed at - runtime). The redundant `styles` prop was removed - use `className`. -- **SegmentPicker**: `onChange` is `(event: React.MouseEvent, value: string)`. -- **Shape** (the `shape` prop on `Button` and `SegmentPicker`): the type is now - `'default' | 'circle'` (was `'' | 'circle'`) - pass `'default'` instead of `''`. -- **Modal**: `className` and any forwarded HTML attributes now apply to the same - root element (previously split across the outer and content elements). -- Transitions moved to the popup element, where Base UI sets - `data-starting-style` / `data-ending-style`. - -### Added - -- `@workflowbuilder/ui/styles.css` - standalone global stylesheet (layer - order, reset, typography) for per-component / subpath consumers. -- `Input` gains a typed `error` prop wired to the error state. -- `Snackbar` exposes proper `role="status"` / `aria-live` semantics. - -### Fixed - -- **DatePicker**: the calendar now ships `react-day-picker`'s stylesheet (it - rendered unstyled before); selection, today, and day-hover states are themed - to the design tokens, and the month-nav chevrons use a neutral color. -- **Accordion**: `onToggleOpen` no longer fires twice when the chevron is - clicked. -- **IconSwitch**: the thumb icon now swaps in uncontrolled mode (it was stuck - on the unchecked icon). -- **Button**: children that match no variant log a clear error instead of - silently rendering nothing. + 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/README.md b/packages/ui/README.md index 40fbe4dcd..33ed98b78 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -154,7 +154,7 @@ To see components rendered live, start the documentation site with `pnpm dev:doc > > Thanks to Base UI, `@workflowbuilder/ui` provides components that are **accessible by default** and **fully customizable** through our design tokens. > -> Earlier `1.0.0` betas were built on the now-deprecated [MUI Base](https://v6.mui.com/base-ui/getting-started/). From `1.0.0-beta.28` the library is built on Base UI; see [CHANGELOG.md](./CHANGELOG.md) for the full list of changes. +> 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 diff --git a/packages/ui/package.json b/packages/ui/package.json index 4778d5bec..3eb977c50 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { "name": "@workflowbuilder/ui", "type": "module", - "version": "1.0.0-beta.28", + "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", From 807657a377d1d27d0434c7656878069cdb7eb402 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Thu, 30 Jul 2026 18:36:54 +0200 Subject: [PATCH 14/42] 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. --- packages/ui/scripts/check-built-css.ts | 225 ++++++++++-- .../edge-label/edge-label-size.module.css | 104 +++--- .../node-as-port-wrapper/node-as-port.css | 16 +- .../node-description.module.css | 32 +- .../node/node-icon/node-icon.module.css | 20 +- .../node/node-panel/handle.module.css | 116 +++--- .../radio-button/radio-size.module.css | 42 +-- .../components/action-buttons.module.css | 12 +- .../snackbar/components/icon.module.css | 46 +-- .../snackbar/components/message.module.css | 28 +- packages/ui/src/styles/typography.css | 334 +++++++++--------- 11 files changed, 585 insertions(+), 390 deletions(-) diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts index 641fd7f53..2d25c8b51 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -1,15 +1,20 @@ /** - * Guard against the WB-222 bug class in built CSS output. + * Guards against two 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. The rule: `var()`'s - * first argument must be a `` (a dashed-ident) - never - * another function. Browsers silently invalidate `var(var(--foo))` and fall - * back, which is how WB-222 shipped a wrong snackbar icon color. + * past review or a build-tool transform introduces it. * - * This is the only line of defense for this bug class today; a source-level - * lint rule (e.g. a stylelint `no-invalid-var`) would catch it earlier but - * none is configured yet. + * 1. `var()`'s first argument must be a `` (a dashed-ident) - + * never another function. Browsers silently invalidate `var(var(--foo))` and fall + * back, which is how WB-222 shipped a wrong snackbar icon color. + * 2. Every component rule must live inside an `@layer` block. 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 `handle.module.css`, `css-layers.md`). + * + * These are the only lines of defense for these bug classes today; source-level lint + * rules (e.g. a stylelint `no-invalid-var` / a custom "no rule outside @layer" rule) + * would catch them earlier but none is configured yet. * * Exits non-zero on any match. */ @@ -20,42 +25,212 @@ import { fileURLToPath } from 'node:url'; const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); const distributionDirectory = path.resolve(currentDirectory, '../dist'); +type Hit = { line: number; column: number; snippet: string }; + +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, ' '); +} + +// --- Check 1: var(var(...)) ------------------------------------------------- + // Matches `var(` followed by optional whitespace and another `var(` — // covers the minified `var(var(` and the unminified `var( var(` / // `var(\n var(`. Comments inside CSS values are uncommon enough that // false positives aren't a real concern. -const pattern = /var\(\s*var\(/g; +const nestedVariablePattern = /var\(\s*var\(/g; -type Hit = { line: number; column: number; snippet: string }; +function checkNoNestedVariable(files: string[]): Array<{ file: string; hits: Hit[] }> { + const failures: Array<{ file: string; hits: Hit[] }> = []; + + for (const file of files) { + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits: Hit[] = []; -const failures: Array<{ file: string; hits: Hit[] }> = []; + for (const match of content.matchAll(nestedVariablePattern)) { + 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 ---------------------------------------- + +/** + * Files copied verbatim from `@workflowbuilder/ui-tokens` (see + * `packages/ui/vite.config.mts`'s static-copy list). They're auto-generated + * design-token primitives (`:root` / `html[data-theme]` blocks of custom + * properties only) and predate/sit outside the `ui.base` / `ui.component` + * contract documented in `css-layers.md` - they're meant to load before it. + */ +const ALLOWLISTED_FILES = new Set(['tokens.css', 'numerals-mode-1.css', 'primitives-mode-1.css']); + +/** + * `date-picker.module.css` documents an intentionally unlayered override (see + * the comment above `.calendar :global(.rdp-root)` in that file) so it beats + * the also-unlayered `react-day-picker/style.css` it themes, by specificity + * rather than load order. Both compile to selectors/keyframes containing + * `rdp-`, so that substring is the stable signal across every dist file the + * date-picker chunk gets bundled into (per-component chunk, the combined + * root `index.css`, etc.) - CSS-module hashes in the scoping prefix change + * between builds, a fixed selector list would not. + */ +const isDeliberateDatePickerOverride = (selector: string): boolean => selector.includes('rdp-'); + +/** Skips a balanced `{ ... }` body (as well as quoted strings inside it) and returns the index just past the closing `}`. */ +function skipBlock(content: string, openBraceIndex: number): number { + let depth = 1; + let cursor = openBraceIndex + 1; + while (cursor < content.length && depth > 0) { + const char = content[cursor]; + switch (char) { + case "'": + case '"': { + const quote = char; + cursor++; + while (cursor < content.length && content[cursor] !== quote) { + if (content[cursor] === '\\') cursor++; + cursor++; + } + break; + } + case '{': { + depth++; + break; + } + case '}': { + depth--; + break; + } + default: { + break; + } + } + cursor++; + } + return cursor; +} -for (const file of globSync('**/*.css', { cwd: distributionDirectory })) { - const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); +function findTopLevelViolations(content: string): Hit[] { const hits: Hit[] = []; + // Comments never carry rules of their own; blank them out (preserving length and + // newlines) so they can't be mistaken for a selector or interfere with offsets. + const source = content.replaceAll(/\/\*[\s\S]*?\*\//g, (match) => match.replaceAll(/[^\n]/g, ' ')); + const length = source.length; + let cursor = 0; + + while (cursor < length) { + const char = source[cursor]; + if (/\s/.test(char)) { + cursor++; + continue; + } + + if (char === '@') { + const nameMatch = /^@[\w-]+/.exec(source.slice(cursor)); + const atRuleName = nameMatch ? nameMatch[0] : '@'; + let headerEnd = cursor; + while (headerEnd < length && source[headerEnd] !== '{' && source[headerEnd] !== ';') headerEnd++; - for (const match of content.matchAll(pattern)) { - const upTo = content.slice(0, match.index); - const line = upTo.split('\n').length; - const column = match.index - upTo.lastIndexOf('\n'); - const snippet = content.slice(Math.max(0, match.index - 12), match.index + 48).replaceAll(/\s+/g, ' '); - hits.push({ line, column, snippet }); + if (source[headerEnd] === ';') { + // A statement, e.g. the `@layer ui.base, ui.component;` order declaration - no body to check. + cursor = headerEnd + 1; + continue; + } + + if (source[headerEnd] === '{') { + const header = source.slice(cursor, headerEnd).trim(); + const blockEnd = skipBlock(source, headerEnd); + // `@layer name { ... }` establishes a layer - everything inside is layered by + // definition, so it's out of scope for this check (its contents are still + // reachable directly by consumers, but that's the CSS-module scoping's job). + if (atRuleName !== '@layer' && !isDeliberateDatePickerOverride(header)) { + hits.push({ ...locate(content, cursor), snippet: snippetAt(content, cursor) }); + } + cursor = blockEnd; + continue; + } + + // Malformed/unterminated at-rule; nothing more to scan. + break; + } + + const openBrace = source.indexOf('{', cursor); + if (openBrace === -1) break; + const selector = source.slice(cursor, openBrace).trim(); + const blockEnd = skipBlock(source, openBrace); + + if (selector !== ':root' && !isDeliberateDatePickerOverride(selector)) { + hits.push({ ...locate(content, cursor), snippet: snippetAt(content, cursor) }); + } + cursor = blockEnd; } - if (hits.length > 0) failures.push({ file, hits }); + return hits; } -if (failures.length === 0) { - console.log('✔ Built CSS: no invalid var(var(...)) calls'); -} else { +function checkLayerCoverage(files: string[]): Array<{ file: string; hits: Hit[] }> { + const failures: Array<{ file: string; hits: Hit[] }> = []; + + for (const file of files) { + if (ALLOWLISTED_FILES.has(path.basename(file))) continue; + + const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); + const hits = findTopLevelViolations(content); + + if (hits.length > 0) failures.push({ file, hits }); + } + + return failures; +} + +// --- Run both checks --------------------------------------------------------- + +function report(title: string, failures: Array<{ file: string; hits: Hit[] }>, 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} invalid var(var(...)) call(s) in built CSS output:\n`); + 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('\nThe first argument of var() must be a --custom-property name. See WB-222.\n'); + console.error(`\n${hint}\n`); + return false; +} + +const files = globSync('**/*.css', { cwd: distributionDirectory }); + +const nestedVariableOk = report( + 'Built CSS: no invalid var(var(...)) calls', + checkNoNestedVariable(files), + 'The first argument of var() must be a --custom-property name. See WB-222.', +); + +const layerCoverageOk = 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). If this is a deliberate exception, allowlist it above ' + + 'with a comment explaining why.', +); + +if (!nestedVariableOk || !layerCoverageOk) { process.exitCode = 1; } 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 index 37397433c..41ddaf78e 100644 --- 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 @@ -26,74 +26,76 @@ --ax-public-edge-label-extra-small-icon-size: 0.875rem; } -.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); +@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; + .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); + 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; + .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); + 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); - } + .text { + &.medium { + padding: var(--ax-public-edge-label-medium-label-padding); + } - &.small { - padding: var(--ax-public-edge-label-small-label-padding); - } + &.small { + padding: var(--ax-public-edge-label-small-label-padding); + } - &.extra-small { - padding: var(--ax-public-edge-label-extra-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); - } + .icon { + &.medium { + padding: var(--ax-public-edge-label-medium-icon-padding); + } - &.small { - padding: var(--ax-public-edge-label-small-icon-padding); - } + &.small { + padding: var(--ax-public-edge-label-small-icon-padding); + } - &.extra-small { - padding: var(--ax-public-edge-label-extra-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); - } + .compound { + &.medium { + padding: var(--ax-public-edge-label-medium-content-padding); + } - &.small { - padding: var(--ax-public-edge-label-small-content-padding); - } + &.small { + padding: var(--ax-public-edge-label-small-content-padding); + } - &.extra-small { - padding: var(--ax-public-edge-label-extra-small-content-padding); + &.extra-small { + padding: var(--ax-public-edge-label-extra-small-content-padding); + } } } 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 index c1635bf1c..3e8916b96 100644 --- 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 @@ -1,8 +1,10 @@ -.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; +@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 index 9cba7887c..b6afffd6f 100644 --- a/packages/ui/src/components/node/node-description/node-description.module.css +++ b/packages/ui/src/components/node/node-description/node-description.module.css @@ -3,23 +3,25 @@ --ax-public-node-title-subtitle: var(--ax-txt-secondary-default); } -.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; +@layer ui.component { + .container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; overflow: hidden; - white-space: nowrap; width: 100%; - } - .subtitle { - color: var(--ax-public-node-title-subtitle); + .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-icon/node-icon.module.css b/packages/ui/src/components/node/node-icon/node-icon.module.css index a42948281..91926b4d5 100644 --- a/packages/ui/src/components/node/node-icon/node-icon.module.css +++ b/packages/ui/src/components/node/node-icon/node-icon.module.css @@ -8,14 +8,16 @@ --ax-public-node-icon-container-background-color: var(--ax-node-bg-secondary-default); } -.container { - display: flex; - padding: var(--ax-public-node-icon-padding); - justify-content: center; - align-items: center; - color: var(--ax-public-node-icon-color); +@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); + 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-panel/handle.module.css b/packages/ui/src/components/node/node-panel/handle.module.css index 017cf7757..301fdfc8b 100644 --- a/packages/ui/src/components/node/node-panel/handle.module.css +++ b/packages/ui/src/components/node/node-panel/handle.module.css @@ -11,73 +11,83 @@ --ax-public-node-port-border-color-hover: var(--ax-node-port-stroke-active); } -.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); +@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-right) { + + :global(.react-flow__handle.react-flow__handle-top) { box-sizing: content-box; - right: calc(-1 * (var(--ax-public-node-padding) + var(--ax-public-node-border-size))); + top: 0; + transform: translate(-50%, -50%); } - :global(.react-flow__handle.react-flow__handle-left) { + :global(.react-flow__handle.react-flow__handle-bottom) { box-sizing: content-box; - left: calc(-1 * (var(--ax-public-node-padding) + var(--ax-public-node-border-size))); + bottom: 0; + transform: translate(-50%, 50%); } - } - :global(.react-flow__handle.react-flow__handle-top) { - box-sizing: content-box; - top: 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); - :global(.react-flow__handle.react-flow__handle-bottom) { - box-sizing: content-box; - bottom: 0; - transform: translate(-50%, 50%); - } + background: var(--ax-public-node-port-background-color); + border: var(--ax-public-node-port-border-size) solid var(--ax-public-node-port-border-color); - :global(.react-flow__handle) { - box-sizing: content-box; - width: var(--ax-public-node-port-size); - height: var(--ax-public-node-port-size); + transition-property: background-color, border-color, width, height; + transition-timing-function: ease-in-out; + transition-duration: 0.1s; + } - background: var(--ax-public-node-port-background-color); - border: var(--ax-public-node-port-border-size) solid var(--ax-public-node-port-border-color); + :global(.react-flow__handle::before) { + content: ''; - transition-property: background-color, border-color, width, height; - transition-timing-function: ease-in-out; - transition-duration: 0.1s; - } + position: absolute; + width: var(--ax-public-node-port-boundary-size); + height: var(--ax-public-node-port-boundary-size); - :global(.react-flow__handle::before) { - content: ''; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); - position: absolute; - width: var(--ax-public-node-port-boundary-size); - height: var(--ax-public-node-port-boundary-size); + border-radius: 50%; + } - top: 50%; - left: 50%; - transform: translate(-50%, -50%); + :global(.connectingfrom) { + background: var(--ax-public-node-port-background-color-hover); + border-color: var(--ax-public-node-port-border-color-hover); - border-radius: 50%; - } + width: var(--ax-public-node-port-size-hover); + height: var(--ax-public-node-port-size-hover); + } - :global(.connectingfrom) { - background: var(--ax-public-node-port-background-color-hover); - border-color: var(--ax-public-node-port-border-color-hover); + :global(.react-flow__handle) { + &:hover { + 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); - } + width: var(--ax-public-node-port-size-hover); + height: var(--ax-public-node-port-size-hover); + } + } - :global(.react-flow__handle) { - &:hover { + &:hover :global(.react-flow__handle.connectionindicator) { background-color: var(--ax-public-node-port-background-color-hover); border-color: var(--ax-public-node-port-border-color-hover); @@ -85,12 +95,4 @@ height: var(--ax-public-node-port-size-hover); } } - - &:hover :global(.react-flow__handle.connectionindicator) { - 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/radio-button/radio-size.module.css b/packages/ui/src/components/radio-button/radio-size.module.css index c6e32352d..02ad89204 100644 --- a/packages/ui/src/components/radio-button/radio-size.module.css +++ b/packages/ui/src/components/radio-button/radio-size.module.css @@ -8,32 +8,34 @@ --ax-public-radio-dot-size-extra-small: 0.5rem /* missing token */; } -.medium { - width: var(--ax-public-radio-size-medium); - height: var(--ax-public-radio-size-medium); +@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); + &::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); + .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); + &::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); + .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); + &::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/snackbar/components/action-buttons.module.css b/packages/ui/src/components/snackbar/components/action-buttons.module.css index ed4d6c8e0..4e32c0171 100644 --- a/packages/ui/src/components/snackbar/components/action-buttons.module.css +++ b/packages/ui/src/components/snackbar/components/action-buttons.module.css @@ -1,6 +1,8 @@ -.container { - display: flex; - align-items: center; - gap: 0.5rem; - margin-left: auto; +@layer ui.component { + .container { + display: flex; + align-items: center; + gap: 0.5rem; + margin-left: auto; + } } diff --git a/packages/ui/src/components/snackbar/components/icon.module.css b/packages/ui/src/components/snackbar/components/icon.module.css index 0070044d2..d969ca01e 100644 --- a/packages/ui/src/components/snackbar/components/icon.module.css +++ b/packages/ui/src/components/snackbar/components/icon.module.css @@ -1,31 +1,33 @@ -.container { - display: flex; - padding-top: 0.125rem; +@layer ui.component { + .container { + display: flex; + padding-top: 0.125rem; - &.center { - align-self: center; - padding-top: 0; - } + &.center { + align-self: center; + padding-top: 0; + } - .status { - width: 1.375rem; - height: 1.375rem; - color: var(--ax-public-snackbar-default-icon-color); + .status { + width: 1.375rem; + height: 1.375rem; + color: var(--ax-public-snackbar-default-icon-color); - &.success { - color: var(--ax-public-snackbar-success-icon-color); - } + &.success { + color: var(--ax-public-snackbar-success-icon-color); + } - &.error { - color: var(--ax-public-snackbar-error-icon-color); - } + &.error { + color: var(--ax-public-snackbar-error-icon-color); + } - &.warning { - color: var(--ax-public-snackbar-warning-icon-color); - } + &.warning { + color: var(--ax-public-snackbar-warning-icon-color); + } - &.info { - color: var(--ax-public-snackbar-info-icon-color); + &.info { + color: var(--ax-public-snackbar-info-icon-color); + } } } } diff --git a/packages/ui/src/components/snackbar/components/message.module.css b/packages/ui/src/components/snackbar/components/message.module.css index b8044d83c..2df188f49 100644 --- a/packages/ui/src/components/snackbar/components/message.module.css +++ b/packages/ui/src/components/snackbar/components/message.module.css @@ -1,16 +1,18 @@ -.container { - display: flex; - flex-direction: column; - text-align: left; - flex: 1; - min-width: 0; - word-break: break-word; -} +@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); -} + .title { + color: var(--ax-public-snackbar-title-color); + } -.subtitle { - color: var(--ax-public-snackbar-subtitle-color); + .subtitle { + color: var(--ax-public-snackbar-subtitle-color); + } } diff --git a/packages/ui/src/styles/typography.css b/packages/ui/src/styles/typography.css index 342c26d82..f14dfa579 100644 --- a/packages/ui/src/styles/typography.css +++ b/packages/ui/src/styles/typography.css @@ -1,167 +1,169 @@ -/* 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%; +@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%; + } } From 0aed076d2e6fd4516fbd60efb5234bf05eb3ac63 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Thu, 30 Jul 2026 18:37:23 +0200 Subject: [PATCH 15/42] 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. --- packages/ui/LICENSE | 201 ++++++++++++++++++ packages/ui/css-layers.md | 2 +- packages/ui/package.json | 8 +- .../components/collapsible/collapsible.tsx | 2 +- .../switch/icon-switch/icon-switch.tsx | 2 +- .../components/tooltip/tooltip-content.tsx | 2 +- .../src/components/tooltip/tooltip-context.ts | 17 ++ .../components/tooltip/tooltip-trigger.tsx | 2 +- .../ui/src/components/tooltip/tooltip.tsx | 21 +- packages/ui/vite.config.mts | 2 +- pnpm-lock.yaml | 6 +- 11 files changed, 238 insertions(+), 27 deletions(-) create mode 100644 packages/ui/LICENSE create mode 100644 packages/ui/src/components/tooltip/tooltip-context.ts 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/css-layers.md b/packages/ui/css-layers.md index e6c34161a..e47b15bc0 100644 --- a/packages/ui/css-layers.md +++ b/packages/ui/css-layers.md @@ -1,6 +1,6 @@ # CSS layers -Overflow UI emits all of its styles into two ordered cascade layers: +`@workflowbuilder/ui` emits all of its styles into two ordered cascade layers: ```css @layer ui.base, ui.component; diff --git a/packages/ui/package.json b/packages/ui/package.json index 3eb977c50..e3ffe6b58 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -15,7 +15,7 @@ "workflow-builder" ], "author": "Synergy Codes", - "license": "MIT", + "license": "Apache-2.0", "homepage": "https://github.com/synergycodes/workflowbuilder", "repository": { "type": "git", @@ -40,6 +40,7 @@ "files": [ "dist", "README.md", + "LICENSE", "CHANGELOG.md", "css-layers.md" ], @@ -60,10 +61,10 @@ }, "dependencies": { "@base-ui/react": "catalog:", + "@phosphor-icons/react": "catalog:", "react-textarea-autosize": "^8.5.6" }, "devDependencies": { - "@phosphor-icons/react": "catalog:", "@types/react": "catalog:", "@types/react-dom": "^19.1.0", "@workflowbuilder/ui-tokens": "workspace:*", @@ -88,6 +89,9 @@ "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, + "publishConfig": { + "access": "public" + }, "sideEffects": [ "**/*.css" ] diff --git a/packages/ui/src/components/collapsible/collapsible.tsx b/packages/ui/src/components/collapsible/collapsible.tsx index f62987e9c..e2fbc59bd 100644 --- a/packages/ui/src/components/collapsible/collapsible.tsx +++ b/packages/ui/src/components/collapsible/collapsible.tsx @@ -23,7 +23,7 @@ function useCollapsibleContext() { console.error(' and must be used within '); } -type CollapsibleProps = { +export type CollapsibleProps = { children: ReactNode; isExpanded?: boolean; defaultExpanded?: boolean; diff --git a/packages/ui/src/components/switch/icon-switch/icon-switch.tsx b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx index 0e7ad1fcb..22a658718 100644 --- a/packages/ui/src/components/switch/icon-switch/icon-switch.tsx +++ b/packages/ui/src/components/switch/icon-switch/icon-switch.tsx @@ -6,7 +6,7 @@ import styles from './icon-switch.module.css'; import { BaseSwitchProps, Switch } from '../switch'; -type IconSwitchProps = WithIcon & { +export type IconSwitchProps = WithIcon & { IconChecked: ReactNode; variant?: Variant; } & BaseSwitchProps; diff --git a/packages/ui/src/components/tooltip/tooltip-content.tsx b/packages/ui/src/components/tooltip/tooltip-content.tsx index f5ef1d606..3c240e6ff 100644 --- a/packages/ui/src/components/tooltip/tooltip-content.tsx +++ b/packages/ui/src/components/tooltip/tooltip-content.tsx @@ -4,7 +4,7 @@ import { forwardRef } from 'react'; import styles from './tooltip.module.css'; -import { useTooltipPlacement } from './tooltip'; +import { useTooltipPlacement } from './tooltip-context'; import { TooltipVariant } from './types'; const TOOLTIP_OFFSET = 10; 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 index b36184c7c..ce0dd606a 100644 --- a/packages/ui/src/components/tooltip/tooltip-trigger.tsx +++ b/packages/ui/src/components/tooltip/tooltip-trigger.tsx @@ -2,7 +2,7 @@ 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'; +import { TOOLTIP_CLOSE_DELAY, TOOLTIP_OPEN_DELAY } from './tooltip-context'; /** * Tooltips trigger is the the element that toggles the tooltip diff --git a/packages/ui/src/components/tooltip/tooltip.tsx b/packages/ui/src/components/tooltip/tooltip.tsx index 35237839a..e231c3d6a 100644 --- a/packages/ui/src/components/tooltip/tooltip.tsx +++ b/packages/ui/src/components/tooltip/tooltip.tsx @@ -1,13 +1,11 @@ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip'; -import { ReactNode, createContext, useContext, useMemo } from 'react'; +import { ReactNode, useMemo } from 'react'; -import { type PlacementContextValue, type TooltipPlacement, placementToSideAlign } from './placement'; +import { type TooltipPlacement, placementToSideAlign } from './placement'; import { TooltipContent } from './tooltip-content'; +import { TooltipPlacementProvider } from './tooltip-context'; import { TooltipTrigger } from './tooltip-trigger'; -export const TOOLTIP_OPEN_DELAY = 500; -export const TOOLTIP_CLOSE_DELAY = 0; - export type { TooltipPlacement } from './placement'; export type TooltipOptions = { @@ -29,15 +27,6 @@ export type TooltipOptions = { onOpenChange?: (open: boolean) => void; }; -const TooltipPlacementContext = createContext({ - side: 'bottom', - align: 'center', -}); - -export function useTooltipPlacement(): PlacementContextValue { - return useContext(TooltipPlacementContext); -} - type Props = { /** * Tooltip reference element. @@ -55,7 +44,7 @@ export function Tooltip({ children, initialOpen, placement = 'bottom', open, onO const placementValue = useMemo(() => placementToSideAlign(placement), [placement]); return ( - + {children} - + ); } diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index d4a170347..de043c823 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -35,7 +35,7 @@ const componentEntries = [ 'tooltip', ] as const; -const externalPackages = ['@base-ui/react', 'react-textarea-autosize']; +const externalPackages = ['@base-ui/react', '@phosphor-icons/react', 'react-textarea-autosize']; const externalModules = new Set(['react', 'react-dom', 'react/jsx-runtime']); function getEntries(): Record { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 950c7404f..f0d1ab47b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -558,13 +558,13 @@ importers: '@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: - '@phosphor-icons/react': - specifier: 'catalog:' - version: 2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@types/react': specifier: 'catalog:' version: 19.1.8 From bcb22e81edf158370b1e6ebe532eea7e693adc0b Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Thu, 30 Jul 2026 18:37:56 +0200 Subject: [PATCH 16/42] 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. --- .github/workflows/pr-check.yml | 18 +++++++++++--- packages/tokens/README.md | 41 +++++++++++++++++++++++++++++++ packages/tokens/eslint.config.mjs | 1 + packages/tokens/package.json | 2 ++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 packages/tokens/README.md create mode 100644 packages/tokens/eslint.config.mjs diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 02f659c3d..9b5c1e730 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -2,9 +2,10 @@ 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 + 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). +# @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: @@ -77,7 +78,7 @@ jobs: run: pnpm --filter @workflowbuilder/sdk build:lib ui: - name: UI lint + typecheck + test + build + name: UI + UI tokens lint + typecheck + test + build runs-on: ubuntu-latest steps: - name: Checkout code @@ -99,6 +100,15 @@ jobs: - 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 diff --git a/packages/tokens/README.md b/packages/tokens/README.md new file mode 100644 index 000000000..b90b8d3bd --- /dev/null +++ b/packages/tokens/README.md @@ -0,0 +1,41 @@ +# @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/eject-tokens.ts` - splits `tokens.json` into one JSON file per theme/primitive set + under `dist/tokens/`. +3. `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']`. +4. `src/generate-css-bundle.ts` - concatenates the primitive and theme CSS into a single + `dist/tokens.css`. + +`config.ts` declares which primitive and theme sets from `tokens.json` get processed. + +`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`). 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/package.json b/packages/tokens/package.json index 112c03553..7d7fa2012 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -9,6 +9,8 @@ "scripts": { "build": "tsx src/index.ts", "prepare": "tsx src/index.ts", + "typecheck": "tsc --noEmit", + "lint": "eslint", "test": "vitest run" }, "devDependencies": { From c3fef52748bcc448907549d0b2aa3e3940e0c0c3 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Thu, 30 Jul 2026 18:38:07 +0200 Subject: [PATCH 17/42] docs: document packages/tokens in CLAUDE.md workspace tables --- CLAUDE.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 962e0ac25..399bf4727 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,6 +71,7 @@ Each workspace has its own context. Read the relevant file before extending a wo | ------------------------- | ------------------------------------------------------- | | `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` | @@ -97,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 From 5292e5bf4a98ccf02f1513ccf7e7a29a27795d35 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Thu, 30 Jul 2026 18:38:26 +0200 Subject: [PATCH 18/42] 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. --- CLAUDE.md | 2 +- apps/docs/src/generated/ui-api.json | 2294 --------------------------- 2 files changed, 1 insertion(+), 2295 deletions(-) delete mode 100644 apps/docs/src/generated/ui-api.json diff --git a/CLAUDE.md b/CLAUDE.md index 399bf4727..6788454bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ 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 | diff --git a/apps/docs/src/generated/ui-api.json b/apps/docs/src/generated/ui-api.json deleted file mode 100644 index 9c96ce391..000000000 --- a/apps/docs/src/generated/ui-api.json +++ /dev/null @@ -1,2294 +0,0 @@ -{ - "accordion": { - "name": "Accordion", - "props": [ - { - "name": "children", - "type": "React.ReactNode", - "required": true, - "default": null, - "description": "Contents of the collapsible section" - }, - { - "name": "defaultOpen", - "type": "boolean", - "required": false, - "default": "true", - "description": "Initial open state" - }, - { - "name": "isOpen", - "type": "boolean", - "required": false, - "default": null, - "description": "True if not collapsed" - }, - { - "name": "label", - "type": "string", - "required": true, - "default": null, - "description": "Text displayed in the header" - }, - { - "name": "onToggleOpen", - "type": "(isOpen: boolean) => void", - "required": false, - "default": null, - "description": "Callback run when the open state changes" - } - ], - "cssVariables": [] - }, - "avatar": { - "name": "Avatar", - "props": [ - { - "name": "imageUrl", - "type": "string", - "required": false, - "default": null, - "description": "Image URL" - }, - { - "name": "size", - "type": "Size", - "required": false, - "default": "'extra-large'", - "description": "Size of the circle container" - }, - { - "name": "username", - "type": "string", - "required": true, - "default": null, - "description": "Provide to use it as alt of the image for better a11y" - } - ], - "cssVariables": [ - { - "name": "--ax-public-avatar-border-color", - "comment": "" - }, - { - "name": "--ax-public-avatar-background-color", - "comment": "" - }, - { - "name": "--ax-public-avatar-border-size", - "comment": "" - } - ] - }, - "button": { - "name": "Button", - "props": [ - { - "name": "size", - "type": "ButtonSize", - "required": false, - "default": null, - "description": "" - }, - { - "name": "tooltip", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "tooltipType", - "type": "TooltipVariant", - "required": false, - "default": null, - "description": "" - }, - { - "name": "variant", - "type": "Variant", - "required": false, - "default": null, - "description": "" - } - ], - "cssVariables": [ - { - "name": "--ax-public-button-background-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-focus-visible-box-shadow-color", - "comment": "" - }, - { - "name": "--ax-public-button-inner-border-color", - "comment": "" - }, - { - "name": "--ax-public-button-color", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-hover", - "comment": "" - }, - { - "name": "--ax-public-button-gap-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-gap-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-size-xx-small", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-xxx-small", - "comment": "missing token" - }, - { - "name": "--ax-public-button-nav-background-color", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-active", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-loader-dot-size", - "comment": "" - }, - { - "name": "--ax-public-button-loader-dot-border-radius", - "comment": "" - }, - { - "name": "--ax-public-button-loader-dot-color", - "comment": "" - }, - { - "name": "--ax-public-button-loader-animation-duration", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-extra-large", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-large", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-medium", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-extra-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-circle", - "comment": "" - }, - { - "name": "--ax-public-button-gap-extra-large", - "comment": "" - }, - { - "name": "--ax-public-button-gap-large", - "comment": "" - }, - { - "name": "--ax-public-button-gap-medium", - "comment": "" - }, - { - "name": "--ax-public-button-gap-small", - "comment": "" - }, - { - "name": "--ax-public-button-gap-extra-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-extra-large", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-large", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-medium", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-extra-small", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-extra-large", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-large", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-medium", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-small", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-extra-small", - "comment": "" - }, - { - "name": "--ax-public-icon-size-extra-large", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-large", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-medium", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-small", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-extra-small", - "comment": "missing token" - }, - { - "name": "--ax-public-label-button-padding-extra-large", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-large", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-medium", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-small", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-extra-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-size", - "comment": "" - }, - { - "name": "--ax-public-button-primary-background", - "comment": "" - }, - { - "name": "--ax-public-button-primary-background-hover", - "comment": "" - }, - { - "name": "--ax-public-button-primary-background-active", - "comment": "" - }, - { - "name": "--ax-public-button-primary-background-focus", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-border-color", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-color", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-border-color-hover", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-color-active", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-border-color-active", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-border-color-focus", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-border-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-secondary-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-background-color", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-border-color", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-color", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-background-color-hover", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-background-color-active", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-background-color-focus", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-background-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-ghost-destructive-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-button-gray-background", - "comment": "" - }, - { - "name": "--ax-public-button-gray-background-hover", - "comment": "" - }, - { - "name": "--ax-public-button-gray-background-active", - "comment": "" - }, - { - "name": "--ax-public-button-gray-background-focus", - "comment": "" - }, - { - "name": "--ax-public-button-error-background", - "comment": "" - }, - { - "name": "--ax-public-button-error-background-hover", - "comment": "" - }, - { - "name": "--ax-public-button-error-background-active", - "comment": "" - }, - { - "name": "--ax-public-button-error-background-focus", - "comment": "" - }, - { - "name": "--ax-public-button-success-background", - "comment": "" - }, - { - "name": "--ax-public-button-success-background-hover", - "comment": "" - }, - { - "name": "--ax-public-button-success-background-active", - "comment": "" - }, - { - "name": "--ax-public-button-success-background-focus", - "comment": "" - }, - { - "name": "--ax-public-button-warning-background", - "comment": "" - }, - { - "name": "--ax-public-button-warning-background-hover", - "comment": "" - }, - { - "name": "--ax-public-button-warning-background-active", - "comment": "" - }, - { - "name": "--ax-public-button-warning-background-focus", - "comment": "" - } - ] - }, - "checkbox": { - "name": "Checkbox", - "props": [ - { - "name": "checked", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the checkbox is checked" - }, - { - "name": "indeterminate", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the checkbox is in an indeterminate state" - }, - { - "name": "size", - "type": "SelectorSize", - "required": false, - "default": "'medium'", - "description": "The size of the checkbox" - } - ], - "cssVariables": [ - { - "name": "--ax-public-checkbox-size-medium", - "comment": "" - }, - { - "name": "--ax-public-checkbox-size-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-size-extra-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-icon-size-medium", - "comment": "" - }, - { - "name": "--ax-public-checkbox-icon-size-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-icon-size-extra-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-border-radius-medium", - "comment": "" - }, - { - "name": "--ax-public-checkbox-border-radius-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-border-radius-extra-small", - "comment": "" - }, - { - "name": "--ax-public-checkbox-border-width", - "comment": "" - }, - { - "name": "--ax-public-checkbox-border-color", - "comment": "" - }, - { - "name": "--ax-public-checkbox-bg", - "comment": "" - }, - { - "name": "--ax-public-checkbox-icon-color", - "comment": "" - }, - { - "name": "--ax-public-checkbox-checked-bg", - "comment": "" - }, - { - "name": "--ax-public-checkbox-focus-border-color", - "comment": "" - }, - { - "name": "--ax-public-checkbox-focus-border-shadow", - "comment": "" - }, - { - "name": "--ax-public-checkbox-disabled-bg", - "comment": "" - }, - { - "name": "--ax-public-checkbox-disabled-border-color", - "comment": "" - }, - { - "name": "--ax-public-checkbox-disabled-icon-color", - "comment": "" - } - ] - }, - "date-picker": { - "name": "DatePicker", - "props": [ - { - "name": "aria-label", - "type": "string", - "required": false, - "default": null, - "description": "Accessible name for the trigger button." - }, - { - "name": "aria-labelledby", - "type": "string", - "required": false, - "default": null, - "description": "`aria-labelledby` for the trigger button." - }, - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "Class name applied to the trigger button." - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": null, - "description": "Disable the picker. The trigger button is not interactive and the\npopover cannot be opened." - }, - { - "name": "id", - "type": "string", - "required": false, - "default": null, - "description": "`id` attribute applied to the trigger button." - }, - { - "name": "inputSize", - "type": "ItemSize", - "required": false, - "default": "'medium'", - "description": "Size variant of the trigger input." - }, - { - "name": "maxDate", - "type": "Date", - "required": false, - "default": null, - "description": "The latest selectable date (inclusive)." - }, - { - "name": "minDate", - "type": "Date", - "required": false, - "default": null, - "description": "The earliest selectable date (inclusive)." - }, - { - "name": "onChange", - "type": "(value: Date | [Date, Date] | Date[] | null) => void", - "required": false, - "default": null, - "description": "Callback fired when the selected value changes.\n\n- `default` -> the selected `Date` or `null` when cleared\n- `range` -> `[from, to]` once both dates are selected, otherwise `null`\n- `multiple`-> the array of selected `Date`s (empty array allowed)" - }, - { - "name": "readOnly", - "type": "boolean", - "required": false, - "default": null, - "description": "Render the picker as read-only. The trigger displays the current value\nbut the popover cannot be opened." - } - ], - "cssVariables": [ - { - "name": "--ax-public-date-picker-dropdown-background", - "comment": "" - }, - { - "name": "--ax-public-date-picker-dropdown-border-color", - "comment": "" - }, - { - "name": "--ax-public-date-picker-dropdown-box-shadow", - "comment": "" - }, - { - "name": "--ax-public-date-picker-color", - "comment": "" - }, - { - "name": "--ax-public-date-picker-header-background", - "comment": "" - }, - { - "name": "--ax-public-date-picker-date-header-color", - "comment": "" - }, - { - "name": "--ax-public-date-picker-date-outside-color", - "comment": "" - }, - { - "name": "--ax-public-date-picker-date-selected-background-color", - "comment": "" - } - ] - }, - "input": { - "name": "Input", - "props": [ - { - "name": "endAdornment", - "type": "ReactNode", - "required": false, - "default": null, - "description": "Element displayed at the end of the input field." - }, - { - "name": "error", - "type": "boolean", - "required": false, - "default": "false", - "description": "Renders the input in an error state." - }, - { - "name": "size", - "type": "ItemSize", - "required": false, - "default": "'medium'", - "description": "Specifies the size of the input field.\nCan be 'small', 'medium', or 'large'." - }, - { - "name": "startAdornment", - "type": "ReactNode", - "required": false, - "default": null, - "description": "Element displayed at the start of the input field." - } - ], - "cssVariables": [ - { - "name": "--ax-public-input-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-input-root-color", - "comment": "" - }, - { - "name": "--ax-public-input-root-border-color", - "comment": "" - }, - { - "name": "--ax-public-input-root-border-color-focus", - "comment": "" - }, - { - "name": "--ax-public-input-root-border-size", - "comment": "" - }, - { - "name": "--ax-public-input-root-background-color-error", - "comment": "" - }, - { - "name": "--ax-public-input-root-border-color-error", - "comment": "" - }, - { - "name": "--ax-public-input-root-color-error", - "comment": "" - }, - { - "name": "--ax-public-input-root-color-disabled", - "comment": "" - } - ] - }, - "menu": { - "name": "Menu", - "props": [ - { - "name": "children", - "type": "ReactElement", - "required": false, - "default": null, - "description": "The trigger element that will open the menu when clicked.\nThis element will be wrapped in a button with appropriate ARIA attributes." - }, - { - "name": "items", - "type": "MenuItemProps[]", - "required": true, - "default": null, - "description": "Array of menu items to be rendered in the menu.\nEach item can be either a regular menu item or a separator." - }, - { - "name": "offset", - "type": "OffsetOptions", - "required": false, - "default": null, - "description": "Distance between a popup and the trigger element" - }, - { - "name": "onOpenChange", - "type": "(open: boolean, event: Event) => void", - "required": false, - "default": null, - "description": "Callback fired when the component requests to be opened or closed.\nReceives the next open state and the native event that triggered the\nchange (if any)." - }, - { - "name": "open", - "type": "boolean", - "required": false, - "default": null, - "description": "Controls whether the menu is open or closed.\nWhen omitted, the menu's open state will be managed internally\nand toggled by clicking on the `children` trigger element." - }, - { - "name": "placement", - "type": "Placement", - "required": false, - "default": "'bottom-end'", - "description": "The preferred placement of the menu relative to its trigger element.\nUses Floating UI placement options." - }, - { - "name": "size", - "type": "ItemSize", - "required": false, - "default": "'medium'", - "description": "Size variant for the menu items." - } - ], - "cssVariables": [] - }, - "modal": { - "name": "Modal", - "props": [ - { - "name": "children", - "type": "ReactNode", - "required": false, - "default": null, - "description": "Content to be displayed in the modal body" - }, - { - "name": "footer", - "type": "ReactNode", - "required": false, - "default": null, - "description": "Content to be displayed in the modal footer" - }, - { - "name": "footerVariant", - "type": "FooterVariant", - "required": false, - "default": "'integrated'", - "description": "Variant of the footer styling" - }, - { - "name": "onClose", - "type": "() => void", - "required": false, - "default": null, - "description": "Callback function called when the modal is closed" - }, - { - "name": "open", - "type": "boolean", - "required": true, - "default": null, - "description": "Controls the visibility of the modal" - }, - { - "name": "size", - "type": "'regular' | 'large'", - "required": false, - "default": "'regular'", - "description": "Size variant of the modal" - }, - { - "name": "subtitle", - "type": "string", - "required": false, - "default": null, - "description": "Optional subtitle displayed below the title" - }, - { - "name": "title", - "type": "string", - "required": true, - "default": null, - "description": "Title displayed in the modal header" - } - ], - "cssVariables": [ - { - "name": "--ax-public-modal-width", - "comment": "" - }, - { - "name": "--ax-public-modal-border-radius", - "comment": "" - }, - { - "name": "--ax-public-modal-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-description-size", - "comment": "" - }, - { - "name": "--ax-public-modal-description-line-height", - "comment": "" - }, - { - "name": "--ax-public-modal-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-content-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-footer-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-background", - "comment": "" - }, - { - "name": "--ax-public-modal-backdrop-background", - "comment": "" - }, - { - "name": "--ax-public-modal-close-button-color", - "comment": "" - }, - { - "name": "--ax-public-modal-header-background", - "comment": "" - }, - { - "name": "--ax-public-modal-header-border-color", - "comment": "" - }, - { - "name": "--ax-public-modal-title-color", - "comment": "" - }, - { - "name": "--ax-public-modal-description-color", - "comment": "" - }, - { - "name": "--ax-public-modal-icon-background", - "comment": "" - }, - { - "name": "--ax-public-modal-icon-color", - "comment": "" - }, - { - "name": "--ax-public-modal-content-background", - "comment": "" - }, - { - "name": "--ax-public-modal-footer-border-color", - "comment": "" - }, - { - "name": "--ax-public-modal-large-width", - "comment": "" - }, - { - "name": "--ax-public-modal-large-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-large-title-size", - "comment": "" - }, - { - "name": "--ax-public-modal-large-title-line-height", - "comment": "" - }, - { - "name": "--ax-public-modal-large-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-modal-box-shadow", - "comment": "" - }, - { - "name": "--ax-public-modal-gap", - "comment": "" - }, - { - "name": "--ax-public-modal-gap-large", - "comment": "" - }, - { - "name": "--ax-public-modal-icon-border-radius", - "comment": "" - }, - { - "name": "--ax-public-modal-icon-border-radius-large", - "comment": "" - }, - { - "name": "--ax-public-modal-content-gap", - "comment": "" - } - ] - }, - "nav-button": { - "name": "NavButton", - "props": [ - { - "name": "isSelected", - "type": "boolean", - "required": false, - "default": null, - "description": "" - }, - { - "name": "size", - "type": "Size", - "required": false, - "default": "'medium'", - "description": "Size variant of the nav button." - }, - { - "name": "tooltip", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "tooltipType", - "type": "TooltipVariant", - "required": false, - "default": null, - "description": "" - } - ], - "cssVariables": [ - { - "name": "--ax-public-button-icon-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-icon-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-hover", - "comment": "" - }, - { - "name": "--ax-public-button-gap-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-gap-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-label-button-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-xx-small", - "comment": "" - }, - { - "name": "--ax-public-label-button-padding-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-xx-small", - "comment": "" - }, - { - "name": "--ax-public-button-border-radius-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-icon-size-xx-small", - "comment": "missing token" - }, - { - "name": "--ax-public-icon-size-xxx-small", - "comment": "missing token" - }, - { - "name": "--ax-public-button-nav-background-color", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-active", - "comment": "" - }, - { - "name": "--ax-public-button-nav-color-disabled", - "comment": "" - } - ] - }, - "radio": { - "name": "Radio", - "props": [ - { - "name": "checked", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the radio button is checked" - }, - { - "name": "name", - "type": "string", - "required": true, - "default": null, - "description": "The name of the radio button group" - }, - { - "name": "onChange", - "type": "(event: React.ChangeEvent) => void", - "required": false, - "default": null, - "description": "Callback fired when the radio button state changes" - }, - { - "name": "size", - "type": "SelectorSize", - "required": false, - "default": "'medium'", - "description": "The size of the radio button" - }, - { - "name": "value", - "type": "string | number", - "required": true, - "default": null, - "description": "The value of the radio button" - } - ], - "cssVariables": [ - { - "name": "--ax-public-radio-size-medium", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-size-small", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-size-extra-small", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-dot-size-medium", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-dot-size-small", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-dot-size-extra-small", - "comment": "missing token" - }, - { - "name": "--ax-public-radio-border-color", - "comment": "" - }, - { - "name": "--ax-public-radio-bg", - "comment": "" - }, - { - "name": "--ax-public-radio-checked-bg", - "comment": "" - }, - { - "name": "--ax-public-radio-dot-color", - "comment": "" - }, - { - "name": "--ax-public-radio-focus-border-color", - "comment": "" - }, - { - "name": "--ax-public-radio-disabled-border-color", - "comment": "" - }, - { - "name": "--ax-public-radio-disabled-bg", - "comment": "" - }, - { - "name": "--ax-public-radio-disabled-dot-color", - "comment": "" - }, - { - "name": "--ax-public-radio-border-radius", - "comment": "" - } - ] - }, - "segment-picker": { - "name": "SegmentPicker", - "props": [ - { - "name": "children", - "type": "ReactElement[]", - "required": true, - "default": null, - "description": "" - }, - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "defaultValue", - "type": "never", - "required": false, - "default": null, - "description": "Must not be used in controlled mode." - }, - { - "name": "onChange", - "type": "(event: MouseEventHandler, value: string) => void", - "required": false, - "default": null, - "description": "" - }, - { - "name": "shape", - "type": "Shape", - "required": false, - "default": "''", - "description": "Controls the shape of the SegmentPicker and its items.\n(default) - Items stretch to fill the container equally.\n'circle' - Items fit tightly around their content to maintain a circular shape.\nOnly supported when items contain icons only." - }, - { - "name": "size", - "type": "Size", - "required": false, - "default": "'medium'", - "description": "Size variant of the SegmentPicker and its items." - }, - { - "name": "value", - "type": "string", - "required": true, - "default": null, - "description": "The currently selected value (controlled mode)." - } - ], - "cssVariables": [ - { - "name": "--ax-public-segment-picker-border-radius-extra-large", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-large", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-medium", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-small", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-extra-small", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-xx-small", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-border-radius-xxx-small", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-gap", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-padding", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-background-color", - "comment": "" - }, - { - "name": "--ax-public-segment-picker-circle-border-radius", - "comment": "" - } - ] - }, - "select": { - "name": "Select", - "props": [ - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "Custom class name for the component." - }, - { - "name": "defaultValue", - "type": "SelectValueType", - "required": false, - "default": null, - "description": "The default value of the select when uncontrolled." - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the select is disabled." - }, - { - "name": "error", - "type": "boolean", - "required": false, - "default": "false", - "description": "Whether the select has an error" - }, - { - "name": "items", - "type": "SelectItem[]", - "required": true, - "default": null, - "description": "List of items to display in the select dropdown" - }, - { - "name": "name", - "type": "string", - "required": false, - "default": null, - "description": "Identifies the field when a form is submitted." - }, - { - "name": "onChange", - "type": "(event: SyntheticEvent | Event | null, value: SelectValueType) => void", - "required": false, - "default": null, - "description": "Callback fired when the value of the select changes." - }, - { - "name": "placeholder", - "type": "string", - "required": false, - "default": null, - "description": "Placeholder text for the select input" - }, - { - "name": "required", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the user must choose a value before submitting a form." - }, - { - "name": "size", - "type": "ItemSize", - "required": false, - "default": "'medium'", - "description": "Size of the select input" - }, - { - "name": "value", - "type": "SelectValueType", - "required": false, - "default": null, - "description": "The controlled value of the select." - } - ], - "cssVariables": [ - { - "name": "--ax-public-select-border-size", - "comment": "" - }, - { - "name": "--ax-public-select-button-color", - "comment": "" - }, - { - "name": "--ax-public-select-button-border-color", - "comment": "" - }, - { - "name": "--ax-public-select-button-border-color-focus", - "comment": "" - }, - { - "name": "--ax-public-select-button-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-select-button-background-color-error", - "comment": "" - }, - { - "name": "--ax-public-select-button-border-color-error", - "comment": "" - }, - { - "name": "--ax-public-select-button-color-error", - "comment": "" - } - ] - }, - "separator": { - "name": "Separator", - "props": [], - "cssVariables": [ - { - "name": "--ax-public-list-item-separator-border-size", - "comment": "" - }, - { - "name": "--ax-public-list-item-separator-background-color", - "comment": "" - } - ] - }, - "snackbar": { - "name": "Snackbar", - "props": [ - { - "name": "buttonLabel", - "type": "string", - "required": false, - "default": null, - "description": "Label for the action button" - }, - { - "name": "close", - "type": "boolean", - "required": false, - "default": "false", - "description": "Whether to show the close button" - }, - { - "name": "onButtonClick", - "type": "() => void", - "required": false, - "default": null, - "description": "Callback fired when the action button is clicked" - }, - { - "name": "onClose", - "type": "() => void", - "required": false, - "default": null, - "description": "Callback fired when the snackbar is closed" - }, - { - "name": "subtitle", - "type": "string", - "required": false, - "default": null, - "description": "Optional secondary message displayed below the title" - }, - { - "name": "title", - "type": "string", - "required": true, - "default": null, - "description": "Main message displayed in the snackbar" - }, - { - "name": "variant", - "type": "SnackbarVariant", - "required": true, - "default": null, - "description": "Visual style variant of the snackbar" - } - ], - "cssVariables": [ - { - "name": "--ax-public-snackbar-border-size", - "comment": "" - }, - { - "name": "--ax-public-snackbar-padding", - "comment": "" - }, - { - "name": "--ax-public-snackbar-border-radius", - "comment": "" - }, - { - "name": "--ax-public-snackbar-content-gap", - "comment": "" - }, - { - "name": "--ax-public-snackbar-width", - "comment": "" - }, - { - "name": "--ax-public-snackbar-title-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-subtitle-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-close-icon-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-default-border", - "comment": "" - }, - { - "name": "--ax-public-snackbar-default-background", - "comment": "" - }, - { - "name": "--ax-public-snackbar-default-icon-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-success-border", - "comment": "" - }, - { - "name": "--ax-public-snackbar-success-background", - "comment": "" - }, - { - "name": "--ax-public-snackbar-success-icon-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-error-border", - "comment": "" - }, - { - "name": "--ax-public-snackbar-error-background", - "comment": "" - }, - { - "name": "--ax-public-snackbar-error-icon-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-warning-border", - "comment": "" - }, - { - "name": "--ax-public-snackbar-warning-background", - "comment": "" - }, - { - "name": "--ax-public-snackbar-warning-icon-color", - "comment": "" - }, - { - "name": "--ax-public-snackbar-info-border", - "comment": "" - }, - { - "name": "--ax-public-snackbar-info-background", - "comment": "" - }, - { - "name": "--ax-public-snackbar-info-icon-color", - "comment": "" - } - ] - }, - "status": { - "name": "Status", - "props": [ - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "Custom class name for the component." - }, - { - "name": "status", - "type": "ValidationStatus", - "required": false, - "default": null, - "description": "The validation status to display." - } - ], - "cssVariables": [ - { - "name": "--ax-public-status-right", - "comment": "missing token" - }, - { - "name": "--ax-public-status-size", - "comment": "missing token" - }, - { - "name": "--ax-public-status-icon-size", - "comment": "missing token" - }, - { - "name": "--ax-public-status-invalid-color", - "comment": "missing token" - }, - { - "name": "--ax-public-status-invalid-background-color", - "comment": "missing token" - } - ] - }, - "switch": { - "name": "Switch", - "props": [ - { - "name": "checked", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the switch is checked or not" - }, - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "Custom class name for the switch component" - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the switch is disabled" - }, - { - "name": "onChange", - "type": "(checked: boolean, event: Event) => void", - "required": false, - "default": null, - "description": "Callback function when the switch state changes" - }, - { - "name": "size", - "type": "SelectorSize", - "required": false, - "default": "'medium'", - "description": "Size of the switch component" - }, - { - "name": "styles", - "type": "string", - "required": false, - "default": null, - "description": "Custom styles to apply to the switch" - }, - { - "name": "thumbChildren", - "type": "React.ReactNode", - "required": false, - "default": null, - "description": "Custom content for the thumb of the switch" - }, - { - "name": "trackChildren", - "type": "React.ReactNode", - "required": false, - "default": null, - "description": "Custom content for the track of the switch" - } - ], - "cssVariables": [ - { - "name": "--ax-public-icon-switch-width", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-height", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-thumb-size", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-thumb-offset", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-translate", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-padding", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-icon-size", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-track-bg", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-thumb-bg-primary", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-icon-color", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-icon-selected-color", - "comment": "" - }, - { - "name": "--ax-public-icon-switch-thumb-bg-secondary", - "comment": "" - }, - { - "name": "--ax-public-switch-width-medium", - "comment": "" - }, - { - "name": "--ax-public-switch-height-medium", - "comment": "" - }, - { - "name": "--ax-public-switch-thumb-padding-medium", - "comment": "" - }, - { - "name": "--ax-public-switch-width-small", - "comment": "" - }, - { - "name": "--ax-public-switch-height-small", - "comment": "" - }, - { - "name": "--ax-public-switch-thumb-padding-small", - "comment": "" - }, - { - "name": "--ax-public-switch-width-extra-small", - "comment": "" - }, - { - "name": "--ax-public-switch-height-extra-small", - "comment": "" - }, - { - "name": "--ax-public-switch-thumb-padding-extra-small", - "comment": "" - }, - { - "name": "--ax-public-switch-outline-color", - "comment": "" - }, - { - "name": "--ax-public-switch-disabled-background-color", - "comment": "" - }, - { - "name": "--ax-public-switch-disabled-border-color", - "comment": "" - }, - { - "name": "--ax-public-track-no-selected-color", - "comment": "" - }, - { - "name": "--ax-public-track-selected-color", - "comment": "" - }, - { - "name": "--ax-public-thumb-no-selected-color", - "comment": "" - }, - { - "name": "--ax-public-thumb-disabled-color", - "comment": "" - }, - { - "name": "--ax-public-thumb-selected-color", - "comment": "" - }, - { - "name": "--ax-public-switch-focus-border-color", - "comment": "" - }, - { - "name": "--ax-public-switch-border-size", - "comment": "" - } - ] - }, - "text-area": { - "name": "TextArea", - "props": [ - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "Custom class name for the textarea" - }, - { - "name": "defaultValue", - "type": "string", - "required": false, - "default": null, - "description": "Initial value of the textarea" - }, - { - "name": "disabled", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the textarea is disabled" - }, - { - "name": "error", - "type": "boolean", - "required": false, - "default": null, - "description": "Whether the textarea has an error" - }, - { - "name": "maxRows", - "type": "number", - "required": false, - "default": null, - "description": "Maximum number of rows the textarea can expand to" - }, - { - "name": "minRows", - "type": "number", - "required": false, - "default": null, - "description": "Minimum number of rows the textarea can expand to" - }, - { - "name": "onBlur", - "type": "(event: React.FocusEvent) => void", - "required": false, - "default": null, - "description": "Function called when the input loses focus.\nThe event parameter may be undefined." - }, - { - "name": "onChange", - "type": "(event: React.ChangeEvent) => void", - "required": false, - "default": null, - "description": "Callback function to handle change in textarea value" - }, - { - "name": "onClick", - "type": "(event: React.MouseEvent) => void", - "required": false, - "default": null, - "description": "Callback function to handle click" - }, - { - "name": "placeholder", - "type": "string", - "required": false, - "default": null, - "description": "Placeholder text for the textarea" - }, - { - "name": "size", - "type": "ItemSize", - "required": false, - "default": "'medium'", - "description": "Size of the textarea" - }, - { - "name": "spellCheck", - "type": "boolean", - "required": false, - "default": null, - "description": "Enables or disables browser spell checking" - }, - { - "name": "value", - "type": "string", - "required": false, - "default": null, - "description": "Controlled value of the textarea" - } - ], - "cssVariables": [ - { - "name": "--ax-public-textarea-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-color", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-border-color", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-border-color-focus", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-border-size", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-background-color-error", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-border-color-error", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-color-error", - "comment": "" - }, - { - "name": "--ax-public-textarea-root-color-disabled", - "comment": "" - } - ] - }, - "tooltip": { - "name": "Tooltip", - "props": [ - { - "name": "children", - "type": "ReactNode", - "required": true, - "default": null, - "description": "Tooltip reference element." - }, - { - "name": "initialOpen", - "type": "boolean", - "required": false, - "default": null, - "description": "If true, the component is shown at initial" - }, - { - "name": "onOpenChange", - "type": "(open: boolean) => void", - "required": false, - "default": null, - "description": "Callback fired when the component requests to be open." - }, - { - "name": "open", - "type": "boolean", - "required": false, - "default": null, - "description": "If true, the component is shown." - }, - { - "name": "placement", - "type": "TooltipPlacement", - "required": false, - "default": null, - "description": "Tooltip placement." - } - ], - "cssVariables": [ - { - "name": "--ax-public-tooltip-default-background", - "comment": "" - }, - { - "name": "--ax-public-tooltip-blue-background", - "comment": "" - }, - { - "name": "--ax-public-tooltip-padding", - "comment": "" - }, - { - "name": "--ax-public-tooltip-radius-size", - "comment": "" - }, - { - "name": "--ax-public-tooltip-text-color", - "comment": "" - }, - { - "name": "--ax-public-tooltip-blue-text-color", - "comment": "" - } - ] - }, - "node-icon": { - "name": "NodeIcon", - "props": [ - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "icon", - "type": "ReactNode", - "required": true, - "default": null, - "description": "" - } - ], - "cssVariables": [ - { - "name": "--ax-public-node-icon-border-size", - "comment": "" - }, - { - "name": "--ax-public-node-icon-border-radius", - "comment": "" - }, - { - "name": "--ax-public-node-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-node-icon-color", - "comment": "" - }, - { - "name": "--ax-public-node-icon-container-border-color", - "comment": "" - }, - { - "name": "--ax-public-node-icon-container-background-color", - "comment": "" - } - ] - }, - "node-description": { - "name": "NodeDescription", - "props": [ - { - "name": "className", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "description", - "type": "string", - "required": false, - "default": null, - "description": "" - }, - { - "name": "label", - "type": "string", - "required": true, - "default": null, - "description": "" - } - ], - "cssVariables": [ - { - "name": "--ax-public-node-title-color", - "comment": "" - }, - { - "name": "--ax-public-node-title-subtitle", - "comment": "" - } - ] - }, - "node-as-port-wrapper": { - "name": "NodeAsPortWrapper", - "props": [ - { - "name": "isConnecting", - "type": "boolean", - "required": true, - "default": null, - "description": "" - }, - { - "name": "offset", - "type": "{ … }", - "required": false, - "default": null, - "description": "" - }, - { - "name": "targetPortPosition", - "type": "Position", - "required": true, - "default": null, - "description": "" - } - ], - "cssVariables": [] - }, - "edge": { - "name": "EdgeLabel", - "props": [ - { - "name": "isHovered", - "type": "boolean", - "required": false, - "default": null, - "description": "" - }, - { - "name": "size", - "type": "EdgeLabelSize", - "required": false, - "default": null, - "description": "" - }, - { - "name": "state", - "type": "EdgeState", - "required": false, - "default": null, - "description": "The visual state of the edge. Determines base styles like `strokeWidth`." - }, - { - "name": "type", - "type": "EdgeLabelType", - "required": false, - "default": null, - "description": "Determines the layout style for the EdgeLabel based on its content:\n\ntext: Simple text label.\nicon: Single icon without additional content.\ncompound: Mixed content like icons + text, multiple icons, etc." - } - ], - "cssVariables": [ - { - "name": "--ax-public-edge-label-medium-label-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-small-label-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-extra-small-label-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-medium-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-small-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-extra-small-icon-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-medium-content-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-small-content-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-extra-small-content-padding", - "comment": "" - }, - { - "name": "--ax-public-edge-label-medium-border-radius", - "comment": "" - }, - { - "name": "--ax-public-edge-label-small-border-radius", - "comment": "" - }, - { - "name": "--ax-public-edge-label-extra-small-border-radius", - "comment": "" - }, - { - "name": "--ax-public-edge-label-medium-icon-size", - "comment": "" - }, - { - "name": "--ax-public-edge-label-small-icon-size", - "comment": "" - }, - { - "name": "--ax-public-edge-label-extra-small-icon-size", - "comment": "" - }, - { - "name": "--ax-public-edge-label-gap", - "comment": "" - }, - { - "name": "--ax-public-edge-background-color", - "comment": "" - }, - { - "name": "--ax-public-edge-label-color", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-size", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-size-selected", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-style", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-color", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-color-selected", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-color-hover", - "comment": "" - }, - { - "name": "--ax-public-edge-label-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-edge-label-border-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-edge-color-disabled", - "comment": "" - }, - { - "name": "--ax-public-edge-color-select", - "comment": "" - }, - { - "name": "--ax-public-edge-color-hover", - "comment": "" - }, - { - "name": "--ax-public-edge-color", - "comment": "" - }, - { - "name": "--ax-public-edge-stroke-width-select", - "comment": "" - }, - { - "name": "--ax-public-edge-stroke-width", - "comment": "" - } - ] - } -} From 4f339a391e40d128319a56084f402e396bb11812 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 31 Jul 2026 10:00:37 +0200 Subject: [PATCH 19/42] chore: sync pnpm-lock.yaml after rebase onto main --- pnpm-lock.yaml | 964 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 865 insertions(+), 99 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0d1ab47b..7765e7588 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,18 +101,12 @@ importers: apps/ai-studio: dependencies: - '@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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + 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 @@ -122,15 +116,33 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + html-to-image: + specifier: 1.11.11 + version: 1.11.11 immer: specifier: ^10.1.1 version: 10.1.1 + mermaid: + specifier: ^11.15.0 + version: 11.16.0 react: specifier: ^19.1.0 version: 19.1.0 react-dom: specifier: 'catalog:' version: 19.1.0(react@19.1.0) + 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-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) + 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.6.0(react@19.1.0)) @@ -149,7 +161,7 @@ importers: 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-svgr: specifier: ^4.3.0 - version: 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)) + version: 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)) 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) @@ -159,6 +171,9 @@ importers: '@hono/node-server': specifier: ^1.14.0 version: 1.19.14(hono@4.12.14) + '@openrouter/ai-sdk-provider': + specifier: ^2.8.0 + version: 2.8.0(ai@6.0.168(zod@4.3.6))(zod@4.3.6) '@temporalio/client': specifier: ^1.11.0 version: 1.16.0 @@ -168,6 +183,9 @@ importers: '@workflow-builder/types': specifier: workspace:* version: link:../../packages/types + ai: + specifier: ^6.0.168 + version: 6.0.168(zod@4.3.6) dotenv: specifier: ^17.4.2 version: 17.4.2 @@ -180,6 +198,9 @@ importers: postgres: specifier: ^3.4.5 version: 3.4.9 + tsx: + specifier: ^4.19.3 + version: 4.21.0 zod: specifier: ^4.3.6 version: 4.3.6 @@ -196,12 +217,6 @@ importers: apps/demo: dependencies: - '@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 @@ -210,7 +225,7 @@ importers: 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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + 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: '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) @@ -229,9 +244,6 @@ importers: i18next: specifier: ^24.2.3 version: 24.2.3(typescript@5.9.3) - i18next-browser-languagedetector: - specifier: ^8.0.5 - version: 8.0.5 immer: specifier: ^10.1.1 version: 10.1.1 @@ -372,6 +384,9 @@ importers: postgres: specifier: ^3.4.5 version: 3.4.9 + tsx: + specifier: ^4.19.3 + version: 4.21.0 devDependencies: '@types/node': specifier: ^22.12.0 @@ -435,12 +450,18 @@ importers: '@fontsource/poppins': specifier: ^5.2.7 version: 5.2.7 + '@jsonforms/core': + specifier: ^3.4.0 + version: 3.5.1 + '@jsonforms/react': + specifier: ^3.4.0 + 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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + 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) @@ -456,6 +477,15 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + i18next: + specifier: ^24.0.0 + version: 24.2.3(typescript@5.9.3) + i18next-browser-languagedetector: + specifier: ^8.0.0 + version: 8.0.5 + immer: + specifier: ^10.0.0 + version: 10.1.1 notistack: specifier: ^3.0.1 version: 3.0.2(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -468,6 +498,9 @@ importers: react-dom: specifier: ^18.0.0 || ^19.0.0 version: 19.1.0(react@19.1.0) + react-i18next: + specifier: ^15.0.0 + 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-mentions-ts: specifier: ^5.4.7 version: 5.4.7(class-variance-authority@0.7.1)(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwind-merge@3.5.0) @@ -475,12 +508,6 @@ importers: specifier: ^2.19.2 version: 2.20.0 devDependencies: - '@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) '@testing-library/react': specifier: ^16.3.0 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.1.1(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -505,18 +532,6 @@ importers: eslint-plugin-tsdoc: specifier: ^0.4.0 version: 0.4.0 - i18next: - specifier: ^24.2.3 - version: 24.2.3(typescript@5.9.3) - i18next-browser-languagedetector: - specifier: ^8.0.5 - version: 8.0.5 - immer: - specifier: ^10.1.1 - version: 10.1.1 - 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) vite: specifier: ^6.0.7 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) @@ -900,6 +915,9 @@ packages: '@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==} @@ -974,6 +992,9 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@commitlint/cli@21.0.1': resolution: {integrity: sha512-8vq10krmbJwBkvzXKhbs4o4JQEVscd3pqOlWuDUaDBwbeL694/P33UC29tZQFTAgPU9fVJ2+f2m3zw16yKWxHg==} engines: {node: '>=22.12.0'} @@ -1781,6 +1802,9 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@iconify/utils@3.1.4': + resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -2121,6 +2145,9 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@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==} @@ -2413,6 +2440,17 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -2621,6 +2659,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} @@ -2892,24 +2933,99 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + '@types/d3-color@3.1.3': resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + '@types/d3-drag@3.0.7': resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + '@types/d3-interpolate@3.0.4': resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@types/d3-selection@3.0.11': resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/d3-transition@3.0.9': resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} '@types/d3-zoom@3.0.8': resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2934,6 +3050,9 @@ packages: '@types/express@5.0.5': resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -3025,6 +3144,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/validator@13.15.4': resolution: {integrity: sha512-LSFfpSnJJY9wbC0LQxgvfb+ynbHftFo0tMsFOl/J4wexLnYMmDSPaj2ZyDv3TkfL1UePxPrxOWJfbiRS8mQv7A==} @@ -3082,6 +3204,9 @@ packages: resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vercel/oidc@3.2.0': resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} @@ -3796,6 +3921,10 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -3871,6 +4000,12 @@ packages: core-js@3.46.0: resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig-typescript-loader@6.3.0: resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} engines: {node: '>=v18'} @@ -3952,10 +4087,51 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.0: + resolution: {integrity: sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + d3-color@3.1.0: resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} engines: {node: '>=12'} + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} @@ -3964,18 +4140,88 @@ packages: resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} engines: {node: '>=12'} + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + d3-ease@3.0.1: resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} engines: {node: '>=12'} + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + d3-interpolate@3.0.1: resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} engines: {node: '>=12'} + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + d3-selection@3.0.0: resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} engines: {node: '>=12'} + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} @@ -3990,6 +4236,13 @@ packages: resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} engines: {node: '>=12'} + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -4012,8 +4265,8 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -4036,6 +4289,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -4076,6 +4332,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -4153,6 +4412,9 @@ packages: dompurify@3.3.0: resolution: {integrity: sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==} + dompurify@3.4.12: + resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==} + domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -4862,6 +5124,9 @@ packages: h3@1.15.5: resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -4977,6 +5242,9 @@ packages: html-to-image@1.11.11: resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -5050,6 +5318,9 @@ packages: immer@10.1.1: resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + immer@11.1.15: + resolution: {integrity: sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -5086,6 +5357,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + inversify@6.0.1: resolution: {integrity: sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ==} @@ -5420,9 +5698,16 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + 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==} @@ -5449,6 +5734,12 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + levn@0.3.0: resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} engines: {node: '>= 0.8.0'} @@ -5580,6 +5871,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -5677,6 +5973,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.16.0: + resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -6121,6 +6420,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -6199,6 +6501,12 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -6369,6 +6677,12 @@ packages: react-is@19.0.0: resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react-mentions-ts@5.4.7: resolution: {integrity: sha512-bTK6joPmyvLckVf1v7vE2xSSeqvL4ZwuzFvGZpt+IrtdDOdFZjiwTUBo5920kiE6WbH/v1PP81xAo6pQ0NQ0Pg==} engines: {node: '>=20'} @@ -6385,6 +6699,18 @@ packages: 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: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -6451,6 +6777,14 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + 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 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -6465,6 +6799,14 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -6616,6 +6958,9 @@ packages: resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} engines: {node: '>= 0.8.15'} + 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'} @@ -6634,6 +6979,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -6648,6 +6996,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -6965,6 +7316,9 @@ packages: stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + suf-log@2.5.3: resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} @@ -7061,6 +7415,9 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -7139,6 +7496,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -7467,6 +7828,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + vite-bundle-analyzer@0.17.1: resolution: {integrity: sha512-ubjLhkuRgOSBNck+6xBbQmjmh8SeLTG4alEM5PX2TNzyGhKLwWlyCz1YG0an3RQnscbhVzSb6kYteoHXhP///A==} hasBin: true @@ -8309,6 +8673,8 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + '@braintree/sanitize-url@7.1.2': {} + '@bundled-es-modules/deepmerge@4.3.2': dependencies: deepmerge: 4.3.1 @@ -8489,6 +8855,8 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 + '@chevrotain/types@11.1.2': {} + '@commitlint/cli@21.0.1(@types/node@22.12.0)(conventional-commits-parser@6.4.0)(typescript@5.6.3)': dependencies: '@commitlint/format': 21.0.1 @@ -8678,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 @@ -8735,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) @@ -9185,6 +9553,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@iconify/utils@3.1.4': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + '@img/colour@1.0.0': optional: true @@ -9488,12 +9862,12 @@ snapshots: 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.13)(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)': 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.13 + dayjs: 1.11.21 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -9503,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 @@ -9547,6 +9921,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@mermaid-js/parser@1.2.0': + dependencies: + '@chevrotain/types': 11.1.2 + '@microsoft/api-extractor-model@7.33.8(@types/node@22.12.0)': dependencies: '@microsoft/tsdoc': 0.16.0 @@ -9595,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) @@ -9611,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) @@ -9632,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 @@ -9641,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 @@ -9654,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) @@ -9674,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 @@ -9810,6 +10188,18 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@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)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.15 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.2.0 + optionalDependencies: + react: 19.1.0 + react-redux: 9.3.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1) + '@rollup/pluginutils@5.3.0(rollup@4.57.1)': dependencies: '@types/estree': 1.0.8 @@ -9991,6 +10381,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.7)': dependencies: '@babel/core': 7.26.7 @@ -10035,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 @@ -10062,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 @@ -10142,12 +10513,12 @@ 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.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@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.13)(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) @@ -10328,18 +10699,81 @@ snapshots: dependencies: '@types/node': 22.12.0 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + '@types/d3-color@3.1.3': {} + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + '@types/d3-drag@3.0.7': dependencies: '@types/d3-selection': 3.0.11 + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + '@types/d3-selection@3.0.11': {} + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/d3-transition@3.0.9': dependencies: '@types/d3-selection': 3.0.11 @@ -10349,6 +10783,39 @@ snapshots: '@types/d3-interpolate': 3.0.4 '@types/d3-selection': 3.0.11 + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@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 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -10384,6 +10851,8 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 1.15.10 + '@types/geojson@7946.0.16': {} + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -10469,6 +10938,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/validator@13.15.4': {} '@types/yauzl@2.10.3': @@ -10555,6 +11026,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vercel/oidc@3.2.0': {} '@vitejs/plugin-react@4.3.4(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))': @@ -11170,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 @@ -11491,6 +11967,8 @@ snapshots: commander@7.2.0: {} + commander@8.3.0: {} + common-ancestor-path@1.0.1: {} compare-func@2.0.0: @@ -11556,6 +12034,14 @@ snapshots: core-js@3.46.0: optional: true + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig-typescript-loader@6.3.0(@types/node@22.12.0)(cosmiconfig@9.0.1(typescript@5.6.3))(typescript@5.6.3): dependencies: '@types/node': 22.12.0 @@ -11571,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 @@ -11655,8 +12132,50 @@ snapshots: csstype@3.1.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.0 + + cytoscape-fcose@2.2.0(cytoscape@3.34.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.0 + + cytoscape@3.34.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + d3-color@3.1.0: {} + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + d3-dispatch@3.0.1: {} d3-drag@3.0.0: @@ -11664,14 +12183,82 @@ snapshots: d3-dispatch: 3.0.1 d3-selection: 3.0.0 + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + d3-ease@3.0.1: {} + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-selection@3.0.0: {} + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + d3-timer@3.0.1: {} d3-transition@3.0.1(d3-selection@3.0.0): @@ -11691,6 +12278,44 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -11718,7 +12343,7 @@ snapshots: date-fns@4.1.0: {} - dayjs@1.11.13: {} + dayjs@1.11.21: {} de-indent@1.0.2: {} @@ -11730,6 +12355,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + decimal.js@10.5.0: {} decode-named-character-reference@1.3.0: @@ -11765,6 +12392,10 @@ snapshots: defu@6.1.4: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} depd@2.0.0: {} @@ -11810,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: @@ -11830,6 +12461,10 @@ snapshots: '@types/trusted-types': 2.0.7 optional: true + dompurify@3.4.12: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -12714,6 +13349,8 @@ snapshots: ufo: 1.6.3 uncrypto: 0.1.3 + hachure-fill@0.5.2: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -12947,6 +13584,8 @@ snapshots: html-to-image@1.11.11: {} + html-url-attributes@3.0.1: {} + html-void-elements@3.0.0: {} html-whitespace-sensitive-tag-names@3.0.1: {} @@ -12996,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: @@ -13022,6 +13661,8 @@ snapshots: immer@10.1.1: {} + immer@11.1.15: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -13049,6 +13690,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + + internmap@2.0.3: {} + inversify@6.0.1: {} iobuffer@5.4.0: {} @@ -13403,10 +14048,16 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + katex@0.16.47: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + klaw-sync@6.0.0: dependencies: graceful-fs: 4.2.11 @@ -13437,6 +14088,10 @@ snapshots: kolorist@1.8.0: {} + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + levn@0.3.0: dependencies: prelude-ls: 1.1.2 @@ -13572,6 +14227,8 @@ snapshots: marked@15.0.12: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} md5@2.3.0: @@ -13798,6 +14455,30 @@ snapshots: merge2@1.4.1: {} + mermaid@11.16.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@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 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.0) + cytoscape-fcose: 2.2.0(cytoscape@3.34.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.21 + dompurify: 3.4.12 + es-toolkit: 1.46.1 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 11.1.0 + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -14453,6 +15134,8 @@ snapshots: path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -14512,6 +15195,13 @@ snapshots: pluralize@8.0.0: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + possible-typed-array-names@1.0.0: {} postcss-calc-ast-parser@0.1.4: @@ -14679,6 +15369,24 @@ snapshots: react-is@19.0.0: {} + react-markdown@10.1.0(@types/react@19.1.8)(react@19.1.0): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.1.8 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react-mentions-ts@5.4.7(class-variance-authority@0.7.1)(clsx@2.1.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwind-merge@3.5.0): dependencies: class-variance-authority: 0.7.1 @@ -14692,6 +15400,15 @@ snapshots: 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 + 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 + react-refresh@0.14.2: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -14732,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 @@ -14756,6 +15473,26 @@ 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): + 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: 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.6.0(react@19.1.0) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -14785,6 +15522,12 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + reflect-metadata@0.1.13: {} reflect.getprototypeof@1.0.10: @@ -15007,6 +15750,8 @@ snapshots: rgbcolor@1.0.1: optional: true + robust-predicates@3.0.3: {} + rollup-plugin-visualizer@7.0.1(rollup@4.57.1): dependencies: open: 11.0.0 @@ -15047,6 +15792,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3 @@ -15065,6 +15817,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + rxjs@7.8.1: dependencies: tslib: 2.8.1 @@ -15472,6 +16226,8 @@ snapshots: stylis@4.2.0: {} + stylis@4.4.0: {} + suf-log@2.5.3: dependencies: s.color: 0.0.15 @@ -15567,6 +16323,8 @@ snapshots: tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinycolor2@1.6.0: {} @@ -15622,6 +16380,8 @@ snapshots: dependencies: typescript: 5.6.3 + ts-dedent@2.3.0: {} + tsconfck@3.1.6(typescript@5.6.3): optionalDependencies: typescript: 5.6.3 @@ -15924,6 +16684,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + vite-bundle-analyzer@0.17.1: {} vite-node@3.0.4(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4): @@ -16001,17 +16778,6 @@ snapshots: 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.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: - '@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)) - 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: - - rollup - - supports-color - - typescript - 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: '@rollup/pluginutils': 5.3.0(rollup@4.57.1) From 0fc78fe27b06b7102f795c77787baa22152baf73 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 31 Jul 2026 15:56:50 +0200 Subject: [PATCH 20/42] fix(ui): require react 19 in peer range --- packages/ui/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index e3ffe6b58..76df167e8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -86,8 +86,8 @@ "vitest": "^3.0.4" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "publishConfig": { "access": "public" From 802b87f8300d38cba717cd9ebb8fd0ad095669bc Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 31 Jul 2026 15:56:52 +0200 Subject: [PATCH 21/42] 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. --- packages/ui/scripts/check-built-css.ts | 114 ++++++++----------------- 1 file changed, 34 insertions(+), 80 deletions(-) diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts index 2d25c8b51..9b73c0629 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -22,6 +22,8 @@ import { globSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import postcss, { type ChildNode } from 'postcss'; + const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); const distributionDirectory = path.resolve(currentDirectory, '../dist'); @@ -85,95 +87,47 @@ const ALLOWLISTED_FILES = new Set(['tokens.css', 'numerals-mode-1.css', 'primiti * root `index.css`, etc.) - CSS-module hashes in the scoping prefix change * between builds, a fixed selector list would not. */ -const isDeliberateDatePickerOverride = (selector: string): boolean => selector.includes('rdp-'); - -/** Skips a balanced `{ ... }` body (as well as quoted strings inside it) and returns the index just past the closing `}`. */ -function skipBlock(content: string, openBraceIndex: number): number { - let depth = 1; - let cursor = openBraceIndex + 1; - while (cursor < content.length && depth > 0) { - const char = content[cursor]; - switch (char) { - case "'": - case '"': { - const quote = char; - cursor++; - while (cursor < content.length && content[cursor] !== quote) { - if (content[cursor] === '\\') cursor++; - cursor++; - } - break; - } - case '{': { - depth++; - break; - } - case '}': { - depth--; - break; - } - default: { - break; - } - } - cursor++; - } - return cursor; +const isDeliberateDatePickerOverride = (header: string): boolean => header.includes('rdp-'); + +function hitFor(node: ChildNode): Hit { + const start = node.source?.start; + return { + line: start?.line ?? 0, + column: start?.column ?? 0, + snippet: node.toString().slice(0, 60).replaceAll(/\s+/g, ' '), + }; } function findTopLevelViolations(content: string): Hit[] { const hits: Hit[] = []; - // Comments never carry rules of their own; blank them out (preserving length and - // newlines) so they can't be mistaken for a selector or interfere with offsets. - const source = content.replaceAll(/\/\*[\s\S]*?\*\//g, (match) => match.replaceAll(/[^\n]/g, ' ')); - const length = source.length; - let cursor = 0; - - while (cursor < length) { - const char = source[cursor]; - if (/\s/.test(char)) { - cursor++; - continue; - } - - if (char === '@') { - const nameMatch = /^@[\w-]+/.exec(source.slice(cursor)); - const atRuleName = nameMatch ? nameMatch[0] : '@'; - let headerEnd = cursor; - while (headerEnd < length && source[headerEnd] !== '{' && source[headerEnd] !== ';') headerEnd++; - if (source[headerEnd] === ';') { - // A statement, e.g. the `@layer ui.base, ui.component;` order declaration - no body to check. - cursor = headerEnd + 1; - continue; + for (const node of postcss.parse(content).nodes) { + switch (node.type) { + case 'atrule': { + // `@layer name { ... }` establishes a layer (everything inside is layered by + // definition) and `@layer a, b;` / `@charset` / `@import` statements carry no + // rules of their own. + const isStatement = node.nodes === undefined; + if (node.name === 'layer' || isStatement || isDeliberateDatePickerOverride(node.params)) break; + hits.push(hitFor(node)); + break; } - - if (source[headerEnd] === '{') { - const header = source.slice(cursor, headerEnd).trim(); - const blockEnd = skipBlock(source, headerEnd); - // `@layer name { ... }` establishes a layer - everything inside is layered by - // definition, so it's out of scope for this check (its contents are still - // reachable directly by consumers, but that's the CSS-module scoping's job). - if (atRuleName !== '@layer' && !isDeliberateDatePickerOverride(header)) { - hits.push({ ...locate(content, cursor), snippet: snippetAt(content, cursor) }); + case 'rule': { + if (isDeliberateDatePickerOverride(node.selector)) break; + // Top-level `:root` blocks are the repo-wide convention for declaring design + // tokens; custom properties don't compete in the cascade the way normal + // declarations do, so they're exempt - but only as long as that's ALL they hold. + if (node.selector === ':root') { + hits.push(...node.nodes.filter((child) => child.type !== 'decl' || !child.prop.startsWith('--')).map(hitFor)); + break; } - cursor = blockEnd; - continue; + hits.push(hitFor(node)); + break; + } + default: { + break; } - - // Malformed/unterminated at-rule; nothing more to scan. - break; - } - - const openBrace = source.indexOf('{', cursor); - if (openBrace === -1) break; - const selector = source.slice(cursor, openBrace).trim(); - const blockEnd = skipBlock(source, openBrace); - - if (selector !== ':root' && !isDeliberateDatePickerOverride(selector)) { - hits.push({ ...locate(content, cursor), snippet: snippetAt(content, cursor) }); } - cursor = blockEnd; } return hits; From 2f56fcb939f94a5e38b98699708f2c1954621c05 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 31 Jul 2026 16:13:19 +0200 Subject: [PATCH 22/42] fix(ui): export WithIcon from the barrel so TypeDoc resolves the icon prop --- packages/ui/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 096a5a7ad..66c82ded0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -26,3 +26,4 @@ export * from './components/tooltip'; // Shared types export * from './shared/types/size'; export * from './shared/types/item-size'; +export * from './shared/types/with-icon'; From 10806dacb355f4c6289420481e8b218f7edc2971 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 31 Jul 2026 16:35:50 +0200 Subject: [PATCH 23/42] fix(ui): pin the select caret to the trigger's far edge --- .../components/select/select-button/select-button.module.css | 4 ++++ 1 file changed, 4 insertions(+) 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 index c6287fe87..e54c8f3a2 100644 --- a/packages/ui/src/components/select/select-button/select-button.module.css +++ b/packages/ui/src/components/select/select-button/select-button.module.css @@ -16,6 +16,10 @@ 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; From 24564dfb5e18bc2593487fd308972ed584bf4566 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 16:06:22 +0200 Subject: [PATCH 24/42] 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 --- packages/tokens/README.md | 20 ++++--- packages/tokens/config.ts | 8 ++- packages/tokens/src/eject-tokens.ts | 19 ++----- packages/tokens/src/generate-css-bundle.ts | 43 +++++--------- packages/tokens/src/index.ts | 9 ++- packages/tokens/src/manifest.spec.ts | 45 +++++++++++++++ packages/tokens/src/manifest.ts | 45 +++++++++++++++ packages/tokens/src/tokens-to-css.ts | 66 +++++++--------------- packages/tokens/src/types.ts | 24 +++++++- 9 files changed, 176 insertions(+), 103 deletions(-) create mode 100644 packages/tokens/src/manifest.spec.ts create mode 100644 packages/tokens/src/manifest.ts diff --git a/packages/tokens/README.md b/packages/tokens/README.md index b90b8d3bd..09496385d 100644 --- a/packages/tokens/README.md +++ b/packages/tokens/README.md @@ -8,15 +8,21 @@ 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/eject-tokens.ts` - splits `tokens.json` into one JSON file per theme/primitive set +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/`. -3. `src/tokens-to-css.ts` - runs each set through [Style Dictionary](https://styledictionary.com/) +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']`. -4. `src/generate-css-bundle.ts` - concatenates the primitive and theme CSS into a single - `dist/tokens.css`. + `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 from `tokens.json` get processed. +`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 @@ -38,4 +44,4 @@ the repo root. pnpm --filter @workflowbuilder/ui-tokens test ``` -Vitest unit tests (currently `src/to-file-name.spec.ts`). +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 index fbe0d5261..6005e97ce 100644 --- a/packages/tokens/config.ts +++ b/packages/tokens/config.ts @@ -1,14 +1,16 @@ 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'], + primitives: ['Numerals/Mode 1', 'Primitives/Mode 1'], themes: [ { - name: 'tokens-dark', + set: 'Tokens/Dark', selector: "html[data-theme='dark']", }, { - name: 'tokens-light', + set: 'Tokens/Light', selector: "html[data-theme='light']", }, ], diff --git a/packages/tokens/src/eject-tokens.ts b/packages/tokens/src/eject-tokens.ts index 65b259f14..e75091cc3 100644 --- a/packages/tokens/src/eject-tokens.ts +++ b/packages/tokens/src/eject-tokens.ts @@ -1,11 +1,10 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; -import path from 'node:path'; import tokens from '../tokens.json'; import { TOKEN_OUTPUT_DIR } from './constants'; -import { toFileName } from './to-file-name'; +import { Manifest } from './types'; -export function ejectTokens(): void { +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. @@ -13,16 +12,10 @@ export function ejectTokens(): void { mkdirSync(TOKEN_OUTPUT_DIR, { recursive: true }); } - // Filter out tokens metadata to get themes - const themes = Object.entries(tokens).filter(([name]) => !name.startsWith('$')); + for (const entry of [...primitives, ...themes]) { + const tokenSet = (tokens as Record)[entry.key]; - for (const [name, theme] of themes) { - const fileName = toFileName(name); - - const outputFilePath = path.join(TOKEN_OUTPUT_DIR, `${fileName}.json`); - console.log(outputFilePath); - - // Write the property value to a new JSON file - writeFileSync(outputFilePath, JSON.stringify(theme, null, 2), 'utf8'); + 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 index 45dfe2400..0a3644048 100644 --- a/packages/tokens/src/generate-css-bundle.ts +++ b/packages/tokens/src/generate-css-bundle.ts @@ -1,32 +1,17 @@ import { readFile, writeFile } from 'node:fs/promises'; -import { config } from '../config'; -import { Theme } from './types'; - -const { primitives, themes } = config; - -export async function generateCSSBundle() { - // Local to the call so repeated invocations in one process don't accumulate. - const codeChunks: string[] = []; - - for (const primitive of primitives) { - codeChunks.push(createPrimitiveImport(primitive)); - } - - for (const theme of themes) { - codeChunks.push(await createThemeImport(theme)); - } - - const code = codeChunks.join('\n\n'); - - return writeFile('./dist/tokens.css', code); -} - -function createPrimitiveImport(name: string) { - return `@import "./${name}.css";`; -} - -async function createThemeImport({ name }: Theme) { - const css = await readFile(`./dist/${name}.css`); - return css.toString(); +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 index a1350c326..647c94fbd 100644 --- a/packages/tokens/src/index.ts +++ b/packages/tokens/src/index.ts @@ -2,7 +2,10 @@ import { tokensToCss } from './tokens-to-css'; import { ejectTokens } from './eject-tokens'; import { generateCSSBundle } from './generate-css-bundle'; +import { buildManifest } from './manifest'; -ejectTokens(); -await tokensToCss(); -await generateCSSBundle(); +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..14aafac18 --- /dev/null +++ b/packages/tokens/src/manifest.spec.ts @@ -0,0 +1,45 @@ +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('does not treat $-metadata keys as exported sets', () => { + expect(() => + buildManifest(exportedKeys, { + primitives: ['$themes'], + themes: [], + }), + ).toThrowError(/'\$themes'/); + }); +}); diff --git a/packages/tokens/src/manifest.ts b/packages/tokens/src/manifest.ts new file mode 100644 index 000000000..de9233bde --- /dev/null +++ b/packages/tokens/src/manifest.ts @@ -0,0 +1,45 @@ +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 missing = [...primitives, ...themes.map((theme) => theme.set)].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(', ')}`, + ); + } + + 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/tokens-to-css.ts b/packages/tokens/src/tokens-to-css.ts index 019499e18..e93d73cdc 100644 --- a/packages/tokens/src/tokens-to-css.ts +++ b/packages/tokens/src/tokens-to-css.ts @@ -1,45 +1,21 @@ import { register } from '@tokens-studio/sd-transforms'; import StyleDictionary, { Config, TransformedToken } from 'style-dictionary'; -import { config } from '../config'; -import { OUTPUT_DIR, TOKEN_OUTPUT_DIR } from './constants'; -import { toFileName } from './to-file-name'; - -const { primitives, themes } = config; +import { OUTPUT_DIR } from './constants'; +import { Manifest, TokenSetEntry } from './types'; register(StyleDictionary); -export async function tokensToCss() { - const primitiveSourceMap = createPrimitiveSourceMap(); - - await processPrimitiveTokens(primitiveSourceMap); - await processThemeTokens(primitiveSourceMap); +export async function tokensToCss(manifest: Manifest) { + await processPrimitiveTokens(manifest.primitives); + await processThemeTokens(manifest); } -function createPrimitiveSourceMap(): Map { - const sourceMap = new Map(); - for (const tokenSet of primitives) { - sourceMap.set(tokenSet, `${TOKEN_OUTPUT_DIR}${tokenSet}.json`); - } - - return sourceMap; -} - -async function processPrimitiveTokens(primitiveSourceMap: Map): Promise { +async function processPrimitiveTokens(primitives: TokenSetEntry[]): Promise { for (const primitive of primitives) { - const themeName = toFileName(primitive); - const sourcePath = primitiveSourceMap.get(primitive); - - if (!sourcePath) { - console.warn(`Source path not found for primitive: ${primitive}`); - continue; - } - - const source = [sourcePath]; - const config = createSDConfig({ - name: themeName, - source, + fileName: primitive.fileName, + source: [primitive.jsonPath], }); const styleDictionary = new StyleDictionary(config); @@ -47,17 +23,15 @@ async function processPrimitiveTokens(primitiveSourceMap: Map): } } -async function processThemeTokens(primitiveSourceMap: Map): Promise { - for (const { name, selector } of themes) { - const themeName = toFileName(name); - const primitiveSources = [...primitiveSourceMap.values()]; - const source = [...primitiveSources, `${TOKEN_OUTPUT_DIR}${name}.json`]; +async function processThemeTokens({ primitives, themes }: Manifest): Promise { + const primitiveSources = primitives.map((primitive) => primitive.jsonPath); + for (const theme of themes) { const config = createSDConfig({ - name: themeName, - source, - selector, - filter: (token) => !primitives.some((primitive) => token.filePath.includes(primitive)), + fileName: theme.fileName, + source: [...primitiveSources, theme.jsonPath], + selector: theme.selector, + filter: (token) => !primitives.some((primitive) => token.filePath.includes(primitive.fileName)), }); const styleDictionary = new StyleDictionary(config); @@ -65,7 +39,7 @@ async function processThemeTokens(primitiveSourceMap: Map): Prom } } -function createSDConfig({ name, selector, source, filter }: SDConfigParams) { +function createSDConfig({ fileName, selector, source, filter }: SDConfigParams) { return { source, preprocessors: ['tokens-studio'], @@ -80,7 +54,7 @@ function createSDConfig({ name, selector, source, filter }: SDConfigParams) { }, files: [ { - destination: `${name}.css`, + destination: `${fileName}.css`, filter, format: 'css/variables', }, @@ -92,7 +66,7 @@ function createSDConfig({ name, selector, source, filter }: SDConfigParams) { } type SDConfigParams = { - name: string; + fileName: string; source: string[]; selector?: string; filter?: (token: TransformedToken) => boolean; @@ -102,6 +76,8 @@ const logOptions = { warnings: 'disabled', // 'warn' | 'error' | 'disabled' verbosity: 'verbose', // 'default' | 'silent' | 'verbose' errors: { - brokenReferences: 'console', // 'throw' | 'console' + // 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 index 8c4808966..c0fac5910 100644 --- a/packages/tokens/src/types.ts +++ b/packages/tokens/src/types.ts @@ -1,9 +1,27 @@ export type Config = { primitives: string[]; - themes: Theme[]; + themes: ThemeConfig[]; }; -export type Theme = { - name: string; +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[]; +}; From ce2cf5160a6d5096d0e0db10d2d3c2ae01a76206 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 16:08:02 +0200 Subject: [PATCH 25/42] 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. --- packages/ui/README.md | 7 ++-- packages/ui/combine-css-bundle.mts | 54 ++++++++++++++++++++++++------ packages/ui/css-layers.md | 53 ++++++++++++++++++----------- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/packages/ui/README.md b/packages/ui/README.md index 33ed98b78..dcc7c27a6 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -46,9 +46,10 @@ 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, so import the global stylesheet once and -> **before any component**, so the cascade layers are ordered correctly: -> `import '@workflowbuilder/ui/styles.css'`. +> 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 diff --git a/packages/ui/combine-css-bundle.mts b/packages/ui/combine-css-bundle.mts index 2461ca66a..4dc26922d 100644 --- a/packages/ui/combine-css-bundle.mts +++ b/packages/ui/combine-css-bundle.mts @@ -6,10 +6,13 @@ 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). - * - * Per-component stylesheets do not carry the @layer declaration; consumers - * establish the order by importing `styles.css` first (or the barrel). + * 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'); @@ -19,8 +22,10 @@ export function combineCssBundle(rootDirectory: string): Plugin { name: 'wb-ui:combine-css-bundle', apply: 'build', closeBundle() { + // Combine before stamping so index.css carries a single statement at its top. writeCombinedStylesheet(distributionDirectory, stylesDirectory); writeGlobalStylesheet(distributionDirectory, stylesDirectory); + prependLayerOrderToAssets(distributionDirectory, stylesDirectory); }, }; } @@ -29,17 +34,33 @@ function readLayerOrder(stylesDirectory: string): string { return fs.readFileSync(path.resolve(stylesDirectory, 'layers.css'), 'utf8').trim(); } -function writeCombinedStylesheet(distributionDirectory: string, stylesDirectory: string) { +function assetsDirectoryOf(distributionDirectory: string): string { const assetsDirectory = path.resolve(distributionDirectory, 'assets'); - if (!fs.existsSync(assetsDirectory)) return; + + 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[] { + return fs + .readdirSync(assetsDirectory) + .filter((file) => file.endsWith('.css')) + .sort(); +} + +function writeCombinedStylesheet(distributionDirectory: string, stylesDirectory: string) { + const assetsDirectory = assetsDirectoryOf(distributionDirectory); // Alphabetical order. Within one cascade layer file order only decides ties // between equal-specificity rules; components don't share selectors, so this // is safe. Replace .sort() with an explicit order if that ever changes. - const styles = fs - .readdirSync(assetsDirectory) - .filter((file) => file.endsWith('.css')) - .sort() + const styles = cssFilesIn(assetsDirectory) .map((file) => fs.readFileSync(path.resolve(assetsDirectory, file), 'utf8')) .join('\n'); @@ -55,3 +76,16 @@ function writeGlobalStylesheet(distributionDirectory: string, stylesDirectory: s 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 index e47b15bc0..8611d6f31 100644 --- a/packages/ui/css-layers.md +++ b/packages/ui/css-layers.md @@ -6,27 +6,40 @@ @layer ui.base, ui.component; ``` -`ui.base` holds resets and primitives; `ui.component` holds component styles. -Declaring the order once, before any rule from either layer, guarantees that -`ui.component` always wins over `ui.base`, and that unlayered consumer styles -win over both. +`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. The declaration lives in -`styles.css` (and in the package barrel, which imports it first), so consumers -establish it by either: - -- importing from the package root - the barrel imports the declaration first; or -- importing `@workflowbuilder/ui/styles.css` **before** any component when - using per-component subpath imports. - -Per-component stylesheets deliberately do **not** repeat the declaration. If one -loads before `styles.css`, the first use of a layer fixes the order: a -`ui.component` rule seen before any `ui.base` rule locks it as -`[ui.component, ui.base]`, inverting the cascade. Importing `styles.css` first -avoids this. - -The combined `index.css` (consumed standalone, e.g. by Workflow Builder) carries -the declaration at its top for the same reason. +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`. + +## 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. From ed026affcedc8b2557e2c4c503888b616791f892 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 16:10:33 +0200 Subject: [PATCH 26/42] 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. --- .../date-picker/date-picker.module.css | 67 +++++++++---------- .../components/date-picker/date-picker.tsx | 5 +- .../src/components/date-picker/variables.css | 5 ++ 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/ui/src/components/date-picker/date-picker.module.css b/packages/ui/src/components/date-picker/date-picker.module.css index 8c14abbd0..9deca6110 100644 --- a/packages/ui/src/components/date-picker/date-picker.module.css +++ b/packages/ui/src/components/date-picker/date-picker.module.css @@ -34,43 +34,42 @@ box-shadow: var(--ax-public-date-picker-dropdown-box-shadow); color: var(--ax-public-date-picker-color); } -} -/* - * react-day-picker theming. These rules are intentionally UNLAYERED and scoped - * under `.calendar` so they win over react-day-picker's own unlayered - * style.css by specificity, regardless of the order the two stylesheets load - * (the docs bundle concatenates component CSS alphabetically, so import order - * is not reliable). `.calendar` is the Popover popup that wraps the calendar. - */ -.calendar :global(.rdp-root) { - --rdp-day_button-border-radius: 0.375rem; - /* drop the default accent ring on selected days - we fill instead */ - --rdp-selected-border: none; - --rdp-today-color: var(--ax-public-date-picker-date-today-color); - /* react-day-picker's default accent is a raw blue; retheme it (range - endpoints, focus rings) to our token instead. */ - --rdp-accent-color: var(--ax-public-date-picker-date-today-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; + /* drop the default accent ring on selected days - we fill instead */ + --rdp-selected-border: none; + --rdp-today-color: var(--ax-public-date-picker-date-today-color); + /* react-day-picker's default accent is a raw blue; retheme it (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; -} + .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); -} + /* 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); + } -/* react-day-picker ships no hover state of its own */ -.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); -} + /* react-day-picker ships no hover state of its own */ + .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); + } -/* selected day: filled background instead of just an accent ring */ -.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); + /* selected day: filled background instead of just an accent ring */ + .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 index 97f0dffa6..94ab9bc81 100644 --- a/packages/ui/src/components/date-picker/date-picker.tsx +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -6,12 +6,11 @@ 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` - without +// it the calendar grid, month caption, and nav render unstyled. import './variables.css'; import inputFontStyles from '@ui/shared/styles/input-font-size.module.css'; import inputSizeStyles from '@ui/shared/styles/input-size.module.css'; -// react-day-picker ships no styles of its own in v9 - this provides the calendar -// grid, month caption, and nav layout. Without it the calendar renders unstyled. -import 'react-day-picker/style.css'; import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; import type { DatePickerProps, DatePickerType } from './types'; diff --git a/packages/ui/src/components/date-picker/variables.css b/packages/ui/src/components/date-picker/variables.css index db3004c4b..35a08c75b 100644 --- a/packages/ui/src/components/date-picker/variables.css +++ b/packages/ui/src/components/date-picker/variables.css @@ -1,3 +1,8 @@ +/* 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); From 6b8492bc4a2e0a28032adfef602c9b9b965231b2 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 16:11:37 +0200 Subject: [PATCH 27/42] 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. --- packages/ui/postcss-box-sizing.mts | 24 +++++++++++-- packages/ui/postcss-box-sizing.spec.mts | 48 +++++++++++++++++++++++++ packages/ui/vite.config.mts | 5 ++- 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 packages/ui/postcss-box-sizing.spec.mts diff --git a/packages/ui/postcss-box-sizing.mts b/packages/ui/postcss-box-sizing.mts index 9a6d0a03d..71c3ac30b 100644 --- a/packages/ui/postcss-box-sizing.mts +++ b/packages/ui/postcss-box-sizing.mts @@ -1,10 +1,29 @@ -import type { Plugin } from 'postcss'; +import type { Plugin, Rule } from 'postcss'; /** - * PostCSS plugin that adds `box-sizing: border-box` to every rule + * 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']); + +const vendorless = (atRuleName: string): string => atRuleName.replace(/^-\w+-/, ''); + +function isPlainStylingContext(rule: Rule): boolean { + for (let parent = rule.parent; parent && parent.type !== 'root'; parent = parent.parent) { + if (parent.type === 'rule') continue; + if (parent.type === 'atrule' && STYLING_CONTEXTS.has(vendorless(parent.name))) continue; + return false; + } + + return true; +} + export function boxSizingPlugin(): Plugin { return { postcssPlugin: 'postcss-box-sizing', @@ -16,6 +35,7 @@ export function boxSizingPlugin(): Plugin { root.walkRules((rule) => { if (rule.selector === ':root') return; + if (!isPlainStylingContext(rule)) return; const hasDeclarations = rule.nodes?.some( (node) => node.type === 'decl', diff --git a/packages/ui/postcss-box-sizing.spec.mts b/packages/ui/postcss-box-sizing.spec.mts new file mode 100644 index 000000000..dce5d0bc1 --- /dev/null +++ b/packages/ui/postcss-box-sizing.spec.mts @@ -0,0 +1,48 @@ +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('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 }'); + }); +}); diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index de043c823..847a8f453 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -120,7 +120,10 @@ export default defineConfig({ libInjectCss(), // Per-entry .d.ts; rollupTypes is intentionally off (incompatible with this // multi-entry setup, see vite-plugin-dts docs). - dts({ entryRoot: 'src', exclude: ['src/**/*.spec.{ts,tsx}'] }), + // Excluding specs is load-bearing: a spec that escapes the filter (e.g. a + // root-level *.spec.mts) drags vitest's type graph into the ts program and + // stalls the build for 15+ minutes with no output. + dts({ entryRoot: 'src', exclude: ['src/**/*.spec.{ts,tsx}', '*.spec.mts'] }), copyTokenStyles(), combineCssBundle(rootDirectory), ...(process.env.BUNDLE_STATS ? bundleStatsPlugins() : []), From f179f5deec8a7de04a097f6e39d31149a356b100 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 16:11:53 +0200 Subject: [PATCH 28/42] 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. --- packages/ui/scripts/check-built-css.ts | 221 +++++++++++++++++++------ 1 file changed, 167 insertions(+), 54 deletions(-) diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts index 9b73c0629..136433113 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -1,5 +1,5 @@ /** - * Guards against two bug classes in built CSS output. + * 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. @@ -10,24 +10,43 @@ * 2. Every component rule must live inside an `@layer` block. 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 `handle.module.css`, `css-layers.md`). + * 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 (e.g. a stylelint `no-invalid-var` / a custom "no rule outside @layer" rule) - * would catch them earlier but none is configured yet. + * rules would catch some of them earlier but none is configured yet. * * Exits non-zero on any match. */ -import { globSync, readFileSync } from 'node:fs'; +import { existsSync, globSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import postcss, { type ChildNode } from 'postcss'; +import postcss, { type AtRule, type ChildNode } from 'postcss'; const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); -const distributionDirectory = path.resolve(currentDirectory, '../dist'); +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[] }; + +// Single source of truth shared with combine-css-bundle.mts, which stamps this +// statement into every built stylesheet. +const layerOrderStatement = postcss.parse(readFileSync(path.resolve(packageDirectory, 'src/styles/layers.css'), 'utf8')) + .first as AtRule; +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); @@ -40,6 +59,17 @@ 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(var(...)) ------------------------------------------------- // Matches `var(` followed by optional whitespace and another `var(` — @@ -48,8 +78,8 @@ function snippetAt(content: string, index: number): string { // false positives aren't a real concern. const nestedVariablePattern = /var\(\s*var\(/g; -function checkNoNestedVariable(files: string[]): Array<{ file: string; hits: Hit[] }> { - const failures: Array<{ file: string; hits: Hit[] }> = []; +function checkNoNestedVariable(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; for (const file of files) { const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); @@ -71,33 +101,12 @@ function checkNoNestedVariable(files: string[]): Array<{ file: string; hits: Hit /** * Files copied verbatim from `@workflowbuilder/ui-tokens` (see * `packages/ui/vite.config.mts`'s static-copy list). They're auto-generated - * design-token primitives (`:root` / `html[data-theme]` blocks of custom + * design-token stylesheets (`:root` / `html[data-theme]` blocks of custom * properties only) and predate/sit outside the `ui.base` / `ui.component` * contract documented in `css-layers.md` - they're meant to load before it. */ const ALLOWLISTED_FILES = new Set(['tokens.css', 'numerals-mode-1.css', 'primitives-mode-1.css']); -/** - * `date-picker.module.css` documents an intentionally unlayered override (see - * the comment above `.calendar :global(.rdp-root)` in that file) so it beats - * the also-unlayered `react-day-picker/style.css` it themes, by specificity - * rather than load order. Both compile to selectors/keyframes containing - * `rdp-`, so that substring is the stable signal across every dist file the - * date-picker chunk gets bundled into (per-component chunk, the combined - * root `index.css`, etc.) - CSS-module hashes in the scoping prefix change - * between builds, a fixed selector list would not. - */ -const isDeliberateDatePickerOverride = (header: string): boolean => header.includes('rdp-'); - -function hitFor(node: ChildNode): Hit { - const start = node.source?.start; - return { - line: start?.line ?? 0, - column: start?.column ?? 0, - snippet: node.toString().slice(0, 60).replaceAll(/\s+/g, ' '), - }; -} - function findTopLevelViolations(content: string): Hit[] { const hits: Hit[] = []; @@ -105,15 +114,14 @@ function findTopLevelViolations(content: string): Hit[] { switch (node.type) { case 'atrule': { // `@layer name { ... }` establishes a layer (everything inside is layered by - // definition) and `@layer a, b;` / `@charset` / `@import` statements carry no + // definition) and `@layer a, b;` / `@charset` statements carry no // rules of their own. const isStatement = node.nodes === undefined; - if (node.name === 'layer' || isStatement || isDeliberateDatePickerOverride(node.params)) break; + if (node.name === 'layer' || isStatement) break; hits.push(hitFor(node)); break; } case 'rule': { - if (isDeliberateDatePickerOverride(node.selector)) break; // Top-level `:root` blocks are the repo-wide convention for declaring design // tokens; custom properties don't compete in the cascade the way normal // declarations do, so they're exempt - but only as long as that's ALL they hold. @@ -133,8 +141,8 @@ function findTopLevelViolations(content: string): Hit[] { return hits; } -function checkLayerCoverage(files: string[]): Array<{ file: string; hits: Hit[] }> { - const failures: Array<{ file: string; hits: Hit[] }> = []; +function checkLayerCoverage(files: string[]): FailureReport[] { + const failures: FailureReport[] = []; for (const file of files) { if (ALLOWLISTED_FILES.has(path.basename(file))) continue; @@ -148,9 +156,96 @@ function checkLayerCoverage(files: string[]): Array<{ file: string; hits: Hit[] return failures; } -// --- Run both checks --------------------------------------------------------- +// --- 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) { + if (ALLOWLISTED_FILES.has(path.basename(file))) continue; + + 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; +} -function report(title: string, failures: Array<{ file: string; hits: Hit[] }>, hint: string): boolean { +// --- 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 -------------- + +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 ?? {})) { + if (typeof target !== 'string' || !target.endsWith('.css')) continue; + if (!existsSync(path.resolve(packageDirectory, target))) { + failures.push({ + file: target, + 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; @@ -170,21 +265,39 @@ function report(title: string, failures: Array<{ file: string; hits: Hit[] }>, h const files = globSync('**/*.css', { cwd: distributionDirectory }); -const nestedVariableOk = report( - 'Built CSS: no invalid var(var(...)) calls', - checkNoNestedVariable(files), - 'The first argument of var() must be a --custom-property name. See WB-222.', -); - -const layerCoverageOk = 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). If this is a deliberate exception, allowlist it above ' + - 'with a comment explaining why.', -); - -if (!nestedVariableOk || !layerCoverageOk) { +const results = [ + report( + 'Built CSS: no invalid var(var(...)) calls', + checkNoNestedVariable(files), + 'The first argument of var() must be a --custom-property name. 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). If this is a deliberate exception, allowlist it above ' + + 'with a comment explaining why.', + ), + 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; } From b631924e7af9c591b1470b40e2d2072c7578f270 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 18:29:53 +0200 Subject: [PATCH 29/42] 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. --- packages/tokens/src/manifest.spec.ts | 9 +++++++++ packages/tokens/src/manifest.ts | 11 ++++++++++- packages/tokens/src/tokens-to-css.ts | 7 ++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/tokens/src/manifest.spec.ts b/packages/tokens/src/manifest.spec.ts index 14aafac18..356767c72 100644 --- a/packages/tokens/src/manifest.spec.ts +++ b/packages/tokens/src/manifest.spec.ts @@ -34,6 +34,15 @@ describe('buildManifest', () => { ).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, { diff --git a/packages/tokens/src/manifest.ts b/packages/tokens/src/manifest.ts index de9233bde..3e31e4ba3 100644 --- a/packages/tokens/src/manifest.ts +++ b/packages/tokens/src/manifest.ts @@ -19,7 +19,9 @@ export function buildManifest( ): Manifest { const exportedSets = new Set(exportedKeys.filter((key) => !key.startsWith('$'))); - const missing = [...primitives, ...themes.map((theme) => theme.set)].filter((key) => !exportedSets.has(key)); + 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(', ')} ` + @@ -27,6 +29,13 @@ export function buildManifest( ); } + // 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`); + } + return { primitives: primitives.map(entryFor), themes: themes.map((theme) => ({ ...entryFor(theme.set), selector: theme.selector })), diff --git a/packages/tokens/src/tokens-to-css.ts b/packages/tokens/src/tokens-to-css.ts index e93d73cdc..05284d4ac 100644 --- a/packages/tokens/src/tokens-to-css.ts +++ b/packages/tokens/src/tokens-to-css.ts @@ -1,4 +1,5 @@ import { register } from '@tokens-studio/sd-transforms'; +import path from 'node:path'; import StyleDictionary, { Config, TransformedToken } from 'style-dictionary'; import { OUTPUT_DIR } from './constants'; @@ -31,7 +32,11 @@ async function processThemeTokens({ primitives, themes }: Manifest): Promise !primitives.some((primitive) => token.filePath.includes(primitive.fileName)), + // 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); From 121eaf6a55711a4be0e02ee11122d3bc74f85ec5 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 18:30:30 +0200 Subject: [PATCH 30/42] 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. --- packages/ui/postcss-box-sizing.mts | 24 +++++++---- packages/ui/postcss-box-sizing.spec.mts | 17 ++++++++ packages/ui/scripts/check-built-css.ts | 56 ++++++++++++++++++------- packages/ui/tsconfig.json | 9 +++- packages/ui/vite.config.mts | 9 ++-- 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/packages/ui/postcss-box-sizing.mts b/packages/ui/postcss-box-sizing.mts index 71c3ac30b..2d3acfb75 100644 --- a/packages/ui/postcss-box-sizing.mts +++ b/packages/ui/postcss-box-sizing.mts @@ -1,4 +1,4 @@ -import type { Plugin, Rule } from 'postcss'; +import type { ChildNode, Document, Plugin, Root, Rule } from 'postcss'; /** * PostCSS plugin that adds `box-sizing: border-box` to every styling rule @@ -12,18 +12,28 @@ import type { Plugin, Rule } from 'postcss'; // (e.g. inside @keyframes) silently becomes animated. const STYLING_CONTEXTS = new Set(['media', 'supports', 'container', 'layer']); -const vendorless = (atRuleName: string): string => atRuleName.replace(/^-\w+-/, ''); +type Ancestor = Document | Root | ChildNode; function isPlainStylingContext(rule: Rule): boolean { - for (let parent = rule.parent; parent && parent.type !== 'root'; parent = parent.parent) { - if (parent.type === 'rule') continue; - if (parent.type === 'atrule' && STYLING_CONTEXTS.has(vendorless(parent.name))) continue; - return false; + 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', @@ -34,7 +44,7 @@ export function boxSizingPlugin(): Plugin { if (!file.endsWith('.module.css')) return; root.walkRules((rule) => { - if (rule.selector === ':root') return; + if (targetsRoot(rule)) return; if (!isPlainStylingContext(rule)) return; const hasDeclarations = rule.nodes?.some( diff --git a/packages/ui/postcss-box-sizing.spec.mts b/packages/ui/postcss-box-sizing.spec.mts index dce5d0bc1..00b14d8b2 100644 --- a/packages/ui/postcss-box-sizing.spec.mts +++ b/packages/ui/postcss-box-sizing.spec.mts @@ -31,6 +31,23 @@ describe('postcss-box-sizing', () => { 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'); diff --git a/packages/ui/scripts/check-built-css.ts b/packages/ui/scripts/check-built-css.ts index 136433113..8f6cf0f7f 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -43,8 +43,12 @@ type FailureReport = { file: string; hits: Hit[] }; // Single source of truth shared with combine-css-bundle.mts, which stamps this // statement into every built stylesheet. -const layerOrderStatement = postcss.parse(readFileSync(path.resolve(packageDirectory, 'src/styles/layers.css'), 'utf8')) - .first as AtRule; +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); @@ -105,6 +109,9 @@ function checkNoNestedVariable(files: string[]): FailureReport[] { * properties only) and predate/sit outside the `ui.base` / `ui.component` * contract documented in `css-layers.md` - they're meant to load before it. */ +// Exact dist-relative paths: the three token files live at the dist root. A +// same-named file anywhere else (e.g. assets/tokens.css from a `tokens` +// entry) gets NO exemption. const ALLOWLISTED_FILES = new Set(['tokens.css', 'numerals-mode-1.css', 'primitives-mode-1.css']); function findTopLevelViolations(content: string): Hit[] { @@ -126,7 +133,11 @@ function findTopLevelViolations(content: string): Hit[] { // tokens; custom properties don't compete in the cascade the way normal // declarations do, so they're exempt - but only as long as that's ALL they hold. if (node.selector === ':root') { - hits.push(...node.nodes.filter((child) => child.type !== 'decl' || !child.prop.startsWith('--')).map(hitFor)); + hits.push( + ...node.nodes + .filter((child) => child.type !== 'comment' && (child.type !== 'decl' || !child.prop.startsWith('--'))) + .map(hitFor), + ); break; } hits.push(hitFor(node)); @@ -145,7 +156,7 @@ function checkLayerCoverage(files: string[]): FailureReport[] { const failures: FailureReport[] = []; for (const file of files) { - if (ALLOWLISTED_FILES.has(path.basename(file))) continue; + if (ALLOWLISTED_FILES.has(file)) continue; const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); const hits = findTopLevelViolations(content); @@ -174,7 +185,7 @@ function checkOrderStatementFirst(files: string[]): FailureReport[] { const failures: FailureReport[] = []; for (const file of files) { - if (ALLOWLISTED_FILES.has(path.basename(file))) continue; + if (ALLOWLISTED_FILES.has(file)) continue; const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); const first = postcss.parse(content).nodes.find((node) => node.type !== 'comment'); @@ -207,6 +218,19 @@ function checkKnownLayerNames(files: string[]): FailureReport[] { // --- 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[] = []; @@ -214,12 +238,16 @@ function checkPublishedSurface(files: string[]): FailureReport[] { exports?: Record; }; for (const [specifier, target] of Object.entries(packageJson.exports ?? {})) { - if (typeof target !== 'string' || !target.endsWith('.css')) continue; - if (!existsSync(path.resolve(packageDirectory, target))) { - failures.push({ - file: target, - hits: [{ line: 0, column: 0, snippet: `missing file for exports["${specifier}"]` }], - }); + 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}"]` }], + }); + } } } @@ -233,9 +261,9 @@ function checkPublishedSurface(files: string[]): FailureReport[] { for (const file of files) { const hits: Hit[] = []; - postcss - .parse(readFileSync(path.resolve(distributionDirectory, file), 'utf8')) - .walkAtRules('import', (atRule) => hits.push(hitFor(atRule))); + postcss.parse(readFileSync(path.resolve(distributionDirectory, file), 'utf8')).walkAtRules('import', (atRule) => { + hits.push(hitFor(atRule)); + }); if (hits.length > 0) failures.push({ file, hits }); } diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 9660df184..391309c09 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -8,6 +8,11 @@ "@ui/*": ["./src/*"] } }, - "include": ["src", "types.d.ts"], - "exclude": ["node_modules", "dist"] + // The named root .mts build files and scripts/ are part of the program on + // purpose: they used to sit in a typecheck blind spot and shipped type + // errors. vite.config.mts stays out (its `.mts`-extension imports need + // allowImportingTsExtensions, and that flag stalls tsc on this program for + // minutes - same pathology as a *.spec.mts reaching vite-plugin-dts). + "include": ["src", "types.d.ts", "postcss-box-sizing.mts", "combine-css-bundle.mts", "scripts"], + "exclude": ["node_modules", "dist", "**/*.spec.mts"] } diff --git a/packages/ui/vite.config.mts b/packages/ui/vite.config.mts index 847a8f453..8b3c21403 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -120,10 +120,11 @@ export default defineConfig({ libInjectCss(), // Per-entry .d.ts; rollupTypes is intentionally off (incompatible with this // multi-entry setup, see vite-plugin-dts docs). - // Excluding specs is load-bearing: a spec that escapes the filter (e.g. a - // root-level *.spec.mts) drags vitest's type graph into the ts program and - // stalls the build for 15+ minutes with no output. - dts({ entryRoot: 'src', exclude: ['src/**/*.spec.{ts,tsx}', '*.spec.mts'] }), + // Excluding specs is load-bearing: with a stray *.spec.mts present in the + // package the build stalled 15+ minutes with no output (observed on a + // root-level spec; the vitest type graph in the ts program is the prime + // suspect). Keep every spec shape excluded. + dts({ entryRoot: 'src', exclude: ['**/*.spec.{ts,tsx,mts}'] }), copyTokenStyles(), combineCssBundle(rootDirectory), ...(process.env.BUNDLE_STATS ? bundleStatsPlugins() : []), From 66ce5d92539907ee365adb2e6156f879afc6a160 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Fri, 7 Aug 2026 18:31:23 +0200 Subject: [PATCH 31/42] 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. --- packages/ui/CHANGELOG.md | 7 +- packages/ui/css-layers.md | 7 ++ .../ui/postcss-box-sizing.decision-log.md | 64 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 packages/ui/postcss-box-sizing.decision-log.md diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index adbe2fecf..d366cdef9 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -30,9 +30,10 @@ Workflow Builder SDK. 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 (add - `@workflowbuilder/ui/styles.css` once for the global layer order, reset, and - typography). + 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`, diff --git a/packages/ui/css-layers.md b/packages/ui/css-layers.md index 8611d6f31..80b9899a8 100644 --- a/packages/ui/css-layers.md +++ b/packages/ui/css-layers.md @@ -36,6 +36,13 @@ stylesheets ignore `@import` entirely. leading with the statement, uses an unknown layer name, or ships rules outside `@layer`. +## 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. 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 From 9d6eebf7f46780c0c3cf28cf91c263a18c821151 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Mon, 10 Aug 2026 21:07:23 +0200 Subject: [PATCH 32/42] 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. --- packages/ui/src/components/input/input.tsx | 8 ++++++++ packages/ui/src/components/text-area/text-area.tsx | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/input/input.tsx b/packages/ui/src/components/input/input.tsx index e2ac6b2f4..7ff09552d 100644 --- a/packages/ui/src/components/input/input.tsx +++ b/packages/ui/src/components/input/input.tsx @@ -28,6 +28,14 @@ export function Input({ }, className, )} + // The padding lives on this wrapper, not on the , so a click in + // the padding area lands here and the field never gains focus - forward + // it (adornment clicks keep their own behavior: target !== currentTarget). + onPointerDown={(event) => { + if (event.target !== event.currentTarget) return; + event.preventDefault(); + event.currentTarget.querySelector('input')?.focus(); + }} > {startAdornment} diff --git a/packages/ui/src/components/text-area/text-area.tsx b/packages/ui/src/components/text-area/text-area.tsx index bc0418b15..fa50bd886 100644 --- a/packages/ui/src/components/text-area/text-area.tsx +++ b/packages/ui/src/components/text-area/text-area.tsx @@ -96,7 +96,16 @@ export function TextArea({ const textareaClasses = clsx(styles['text-area'], inputFontStyles[size]); return ( -
+
- forward clicks + // in the padding area so the field gains focus (see input.tsx). + onPointerDown={(event) => { + if (event.target !== event.currentTarget) return; + event.preventDefault(); + event.currentTarget.querySelector('textarea')?.focus(); + }} + > Date: Mon, 10 Aug 2026 21:11:12 +0200 Subject: [PATCH 33/42] 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. --- packages/ui/src/components/modal/modal.module.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/modal/modal.module.css b/packages/ui/src/components/modal/modal.module.css index 439fa46dc..eae8052f2 100644 --- a/packages/ui/src/components/modal/modal.module.css +++ b/packages/ui/src/components/modal/modal.module.css @@ -58,8 +58,8 @@ overflow: auto; transition: - opacity 200ms ease-out, - transform 200ms ease-out; + opacity 200ms ease, + transform 200ms ease; &[data-starting-style], &[data-ending-style] { @@ -78,7 +78,7 @@ inset: 0; background-color: color-mix(in srgb, var(--ax-public-modal-backdrop-background), transparent 50%); - transition: opacity 200ms ease-out; + transition: opacity 200ms ease; &[data-starting-style], &[data-ending-style] { From ea508d550b896fbb8a9a866d62155b3095a4f304 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Mon, 10 Aug 2026 22:31:23 +0200 Subject: [PATCH 34/42] 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. --- packages/ui/package.json | 2 +- packages/ui/tsconfig.json | 9 ++------- packages/ui/tsconfig.node.json | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 packages/ui/tsconfig.node.json diff --git a/packages/ui/package.json b/packages/ui/package.json index 76df167e8..9a8b848f9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -32,7 +32,7 @@ "check:built-css": "tsx ./scripts/check-built-css.ts", "lint": "eslint", "lint:fix": "eslint --fix", - "typecheck": "tsc --noEmit --pretty", + "typecheck": "tsc --noEmit --pretty && tsc --noEmit --pretty -p tsconfig.node.json", "test": "vitest run", "build:stats": "BUNDLE_STATS=1 vite build" }, diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 391309c09..9660df184 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -8,11 +8,6 @@ "@ui/*": ["./src/*"] } }, - // The named root .mts build files and scripts/ are part of the program on - // purpose: they used to sit in a typecheck blind spot and shipped type - // errors. vite.config.mts stays out (its `.mts`-extension imports need - // allowImportingTsExtensions, and that flag stalls tsc on this program for - // minutes - same pathology as a *.spec.mts reaching vite-plugin-dts). - "include": ["src", "types.d.ts", "postcss-box-sizing.mts", "combine-css-bundle.mts", "scripts"], - "exclude": ["node_modules", "dist", "**/*.spec.mts"] + "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..f12f752bc --- /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", "combine-css-bundle.mts", "scripts"], + "exclude": ["node_modules", "dist", "**/*.spec.mts"] +} From e1c65eac67e8701a694d13451edd04a167188490 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:54:41 +0200 Subject: [PATCH 35/42] 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. --- packages/ui/postcss-box-sizing.spec.mts | 11 +++++++++++ .../src/components/node/node-panel/handle.module.css | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/packages/ui/postcss-box-sizing.spec.mts b/packages/ui/postcss-box-sizing.spec.mts index 00b14d8b2..e17bb444e 100644 --- a/packages/ui/postcss-box-sizing.spec.mts +++ b/packages/ui/postcss-box-sizing.spec.mts @@ -62,4 +62,15 @@ describe('postcss-box-sizing', () => { 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/src/components/node/node-panel/handle.module.css b/packages/ui/src/components/node/node-panel/handle.module.css index 301fdfc8b..4ae8d6a55 100644 --- a/packages/ui/src/components/node/node-panel/handle.module.css +++ b/packages/ui/src/components/node/node-panel/handle.module.css @@ -69,7 +69,11 @@ 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); @@ -79,6 +83,7 @@ :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); @@ -88,6 +93,7 @@ } &: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); From da84b32dc04f369e09d14160c788d2c9a943099e Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:55:02 +0200 Subject: [PATCH 36/42] fix(tokens): fail the build when set keys collide after file-name normalization --- packages/tokens/src/manifest.spec.ts | 9 +++++++++ packages/tokens/src/manifest.ts | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/tokens/src/manifest.spec.ts b/packages/tokens/src/manifest.spec.ts index 356767c72..087610815 100644 --- a/packages/tokens/src/manifest.spec.ts +++ b/packages/tokens/src/manifest.spec.ts @@ -51,4 +51,13 @@ describe('buildManifest', () => { }), ).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 index 3e31e4ba3..af4fd10ca 100644 --- a/packages/tokens/src/manifest.ts +++ b/packages/tokens/src/manifest.ts @@ -36,6 +36,23 @@ export function buildManifest( 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 })), From 47b7fabdd3ea280cb5fdbc9fc385103e27f76989 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:55:06 +0200 Subject: [PATCH 37/42] fix(tokens): clean dist before building so renamed sets leave no stale output --- packages/tokens/src/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts index 647c94fbd..832b3ac58 100644 --- a/packages/tokens/src/index.ts +++ b/packages/tokens/src/index.ts @@ -1,9 +1,16 @@ +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); From 2389793abf524ea7dafe210789e1494c8be52544 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:55:09 +0200 Subject: [PATCH 38/42] fix(ui): fail the combine step when dist/assets holds no component CSS --- packages/ui/combine-css-bundle.mts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ui/combine-css-bundle.mts b/packages/ui/combine-css-bundle.mts index 4dc26922d..6d0dcb930 100644 --- a/packages/ui/combine-css-bundle.mts +++ b/packages/ui/combine-css-bundle.mts @@ -48,10 +48,19 @@ function assetsDirectoryOf(distributionDirectory: string): string { } function cssFilesIn(assetsDirectory: string): string[] { - return fs + 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) { From c9619be099431d00f07427a00ec1cb38d37c3d4b Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:55:25 +0200 Subject: [PATCH 39/42] 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)). --- .changeset/ui-layer-root-defaults.md | 5 ++ packages/ui/css-layers.md | 11 +++ packages/ui/postcss-layer-root-defaults.mts | 42 +++++++++++ .../ui/postcss-layer-root-defaults.spec.mts | 39 ++++++++++ packages/ui/scripts/check-built-css.ts | 73 +++++++------------ packages/ui/src/styles/globals.css | 8 +- packages/ui/tsconfig.node.json | 2 +- packages/ui/vite.config.mts | 20 ++++- 8 files changed, 150 insertions(+), 50 deletions(-) create mode 100644 .changeset/ui-layer-root-defaults.md create mode 100644 packages/ui/postcss-layer-root-defaults.mts create mode 100644 packages/ui/postcss-layer-root-defaults.spec.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/packages/ui/css-layers.md b/packages/ui/css-layers.md index 80b9899a8..4e478ed4b 100644 --- a/packages/ui/css-layers.md +++ b/packages/ui/css-layers.md @@ -36,6 +36,17 @@ stylesheets ignore `@import` entirely. 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 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 index 8f6cf0f7f..3ef3922bd 100644 --- a/packages/ui/scripts/check-built-css.ts +++ b/packages/ui/scripts/check-built-css.ts @@ -5,12 +5,15 @@ * past review or a build-tool transform introduces it. * * 1. `var()`'s first argument must be a `` (a dashed-ident) - - * never another function. Browsers silently invalidate `var(var(--foo))` and fall - * back, which is how WB-222 shipped a wrong snackbar icon color. - * 2. Every component rule must live inside an `@layer` block. 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`). + * 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) - @@ -74,22 +77,22 @@ function hitFor(node: ChildNode | undefined): Hit { }; } -// --- Check 1: var(var(...)) ------------------------------------------------- +// --- Check 1: var() first argument is a dashed ident -------------------------- -// Matches `var(` followed by optional whitespace and another `var(` — -// covers the minified `var(var(` and the unminified `var( var(` / -// `var(\n var(`. Comments inside CSS values are uncommon enough that -// false positives aren't a real concern. -const nestedVariablePattern = /var\(\s*var\(/g; +// Matches `var(` whose first argument does not start with `--`: another +// function (`var(var(`), an undashed name (`var(ax-color)`) or an empty call +// (`var()`), minified or not. Comments inside CSS values are uncommon enough +// that false positives aren't a real concern. +const malformedVariablePattern = /var\(\s*(?!--)/g; -function checkNoNestedVariable(files: string[]): FailureReport[] { +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(nestedVariablePattern)) { + for (const match of content.matchAll(malformedVariablePattern)) { const { line, column } = locate(content, match.index); hits.push({ line, column, snippet: snippetAt(content, match.index) }); } @@ -102,18 +105,10 @@ function checkNoNestedVariable(files: string[]): FailureReport[] { // --- Check 2: no rule outside @layer ---------------------------------------- -/** - * Files copied verbatim from `@workflowbuilder/ui-tokens` (see - * `packages/ui/vite.config.mts`'s static-copy list). They're auto-generated - * design-token stylesheets (`:root` / `html[data-theme]` blocks of custom - * properties only) and predate/sit outside the `ui.base` / `ui.component` - * contract documented in `css-layers.md` - they're meant to load before it. - */ -// Exact dist-relative paths: the three token files live at the dist root. A -// same-named file anywhere else (e.g. assets/tokens.css from a `tokens` -// entry) gets NO exemption. -const ALLOWLISTED_FILES = new Set(['tokens.css', 'numerals-mode-1.css', 'primitives-mode-1.css']); - +// No exemptions: `:root` variable defaults are layered too (the build wraps +// them in `ui.base` - see postcss-layer-root-defaults.mts and the token-copy +// transform in vite.config.mts), so consumer overrides win by layer rules, +// not by load order. function findTopLevelViolations(content: string): Hit[] { const hits: Hit[] = []; @@ -129,17 +124,6 @@ function findTopLevelViolations(content: string): Hit[] { break; } case 'rule': { - // Top-level `:root` blocks are the repo-wide convention for declaring design - // tokens; custom properties don't compete in the cascade the way normal - // declarations do, so they're exempt - but only as long as that's ALL they hold. - if (node.selector === ':root') { - hits.push( - ...node.nodes - .filter((child) => child.type !== 'comment' && (child.type !== 'decl' || !child.prop.startsWith('--'))) - .map(hitFor), - ); - break; - } hits.push(hitFor(node)); break; } @@ -156,8 +140,6 @@ function checkLayerCoverage(files: string[]): FailureReport[] { const failures: FailureReport[] = []; for (const file of files) { - if (ALLOWLISTED_FILES.has(file)) continue; - const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); const hits = findTopLevelViolations(content); @@ -185,8 +167,6 @@ function checkOrderStatementFirst(files: string[]): FailureReport[] { const failures: FailureReport[] = []; for (const file of files) { - if (ALLOWLISTED_FILES.has(file)) continue; - const content = readFileSync(path.resolve(distributionDirectory, file), 'utf8'); const first = postcss.parse(content).nodes.find((node) => node.type !== 'comment'); @@ -295,17 +275,18 @@ const files = globSync('**/*.css', { cwd: distributionDirectory }); const results = [ report( - 'Built CSS: no invalid var(var(...)) calls', - checkNoNestedVariable(files), - 'The first argument of var() must be a --custom-property name. See WB-222.', + '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). If this is a deliberate exception, allowlist it above ' + - 'with a comment explaining why.', + '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', diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css index 20032f81c..45d105cc3 100644 --- a/packages/ui/src/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -1,3 +1,7 @@ -:root { - --ax-public-transition: 0.1s ease-in; +/* 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/tsconfig.node.json b/packages/ui/tsconfig.node.json index f12f752bc..30bdc249a 100644 --- a/packages/ui/tsconfig.node.json +++ b/packages/ui/tsconfig.node.json @@ -9,6 +9,6 @@ // 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", "combine-css-bundle.mts", "scripts"], + "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/vite.config.mts b/packages/ui/vite.config.mts index 8b3c21403..b499e2814 100644 --- a/packages/ui/vite.config.mts +++ b/packages/ui/vite.config.mts @@ -1,3 +1,4 @@ +import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { visualizer } from 'rollup-plugin-visualizer'; @@ -9,6 +10,7 @@ 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)); @@ -55,10 +57,26 @@ function isExternal(id: string): boolean { 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`', + ); + } + } + + // The generated token files are raw `:root` / `html[data-theme]` blocks; + // shipping them means applying this package's layer contract: lead with the + // order statement, put the defaults in ui.base so consumer overrides win. + 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}`, })), }); } @@ -108,7 +126,7 @@ export default defineConfig({ css: { devSourcemap: true, postcss: { - plugins: [boxSizingPlugin()], + plugins: [boxSizingPlugin(), layerRootDefaultsPlugin()], }, }, resolve: { From 31af28b745f340aae2dc990801e1bba4bdad0ff0 Mon Sep 17 00:00:00 2001 From: librowski-synergy Date: Tue, 11 Aug 2026 11:55:46 +0200 Subject: [PATCH 40/42] 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. --- .changeset/ui-react-18-peer.md | 5 +++++ packages/ui/package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/ui-react-18-peer.md 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/packages/ui/package.json b/packages/ui/package.json index 9a8b848f9..2edd03db7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -86,8 +86,8 @@ "vitest": "^3.0.4" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" }, "publishConfig": { "access": "public" From f4b157a4f64ec4e61416d1e3325b21b08a8400a8 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Wed, 12 Aug 2026 14:22:42 +0200 Subject: [PATCH 41/42] 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. --- .../ui/src/components/modal/modal.module.css | 60 +++++++------------ packages/ui/src/components/modal/modal.tsx | 47 ++++++++------- 2 files changed, 46 insertions(+), 61 deletions(-) diff --git a/packages/ui/src/components/modal/modal.module.css b/packages/ui/src/components/modal/modal.module.css index eae8052f2..cb9fc6fad 100644 --- a/packages/ui/src/components/modal/modal.module.css +++ b/packages/ui/src/components/modal/modal.module.css @@ -43,31 +43,6 @@ } @layer ui.component { - .modal-base { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; - height: 100vh; - padding: 1.5rem; - overflow: auto; - - transition: - opacity 200ms ease, - transform 200ms ease; - - &[data-starting-style], - &[data-ending-style] { - opacity: 0; - transform: scale(0.96); - } - } - .backdrop { position: fixed; top: 0; @@ -86,29 +61,35 @@ } } - .backdrop-close { - all: unset; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - .modal { - position: relative; - z-index: 2; - margin: auto; + 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); - z-index: 1001; 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; @@ -161,12 +142,15 @@ 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; diff --git a/packages/ui/src/components/modal/modal.tsx b/packages/ui/src/components/modal/modal.tsx index 36298d9c9..953d33a9e 100644 --- a/packages/ui/src/components/modal/modal.tsx +++ b/packages/ui/src/components/modal/modal.tsx @@ -76,33 +76,34 @@ export const Modal = forwardRef( > - -
-
-
- {icon &&
{icon}
} -
- }> - {title} - - {subtitle && ( - }> - {subtitle} - - )} -
+ {/* 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 && ( - - - - )}
+ {onClose && ( + + + + )} +
- {children &&
{children}
} + {children &&
{children}
} - {footer &&
{footer}
} -
+ {footer &&
{footer}
} From 4d9eb3ef4eb071234be959da7d07f850a93fbd90 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Wed, 12 Aug 2026 14:22:58 +0200 Subject: [PATCH 42/42] 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. --- packages/ui/src/components/date-picker/date-picker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/date-picker/date-picker.tsx b/packages/ui/src/components/date-picker/date-picker.tsx index 94ab9bc81..3931f4479 100644 --- a/packages/ui/src/components/date-picker/date-picker.tsx +++ b/packages/ui/src/components/date-picker/date-picker.tsx @@ -11,6 +11,7 @@ import styles from './date-picker.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 listBoxStyles from '@ui/shared/styles/list-box.module.css'; import { dayjsTokenToDateFns, isDateTuple, normalizeInitialValue } from './date-utils'; import type { DatePickerProps, DatePickerType } from './types'; @@ -252,7 +253,7 @@ export const DatePicker = forwardRef(function DatePick {triggerLabel ?? placeholder} - + {calendar}