diff --git a/CLAUDE.md b/CLAUDE.md index 6cf746e34..5b41e7f98 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. First run, or after schema changes | -| `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. First run, or after schema changes | +| `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, and `db:migrate` on the first run. @@ -52,6 +52,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 ``` @@ -62,20 +64,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 @@ -130,7 +134,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. @@ -155,7 +159,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 2c154bab6..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 @@ -127,7 +133,7 @@ importers: version: 19.1.0(react@19.1.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/react': specifier: 'catalog:' @@ -252,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 @@ -501,7 +507,7 @@ importers: version: 0.4.0 i18next: specifier: ^24.2.3 - version: 24.2.3(typescript@5.6.3) + version: 24.2.3(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.0.5 version: 8.0.5 @@ -510,25 +516,107 @@ importers: version: 10.1.1 react-i18next: specifier: ^15.4.1 - version: 15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.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) 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) vite-plugin-dts: specifier: ^4.5.0 - 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)) + version: 4.5.4(@types/node@22.12.0)(rollup@4.57.1)(typescript@5.9.3)(vite@6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4)) vite-plugin-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) 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': @@ -560,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 @@ -701,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'} @@ -717,6 +867,45 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@base-ui/react@1.4.1': + resolution: {integrity: sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@date-fns/tz': ^1.2.0 + '@types/react': ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@date-fns/tz': + optional: true + '@types/react': + optional: true + date-fns: + optional: true + + '@base-ui/utils@0.2.8': + resolution: {integrity: sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ==} + peerDependencies: + '@types/react': ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@bundled-es-modules/deepmerge@4.3.2': + resolution: {integrity: sha512-q8doe7ndrY2IolUOFIn0A0++JBX38pMhN7kFhTF4cnjIcILf6X6H2yWczInyv8ZFdR0lrE8088X8XS5efxXz8A==} + + '@bundled-es-modules/glob@10.4.2': + resolution: {integrity: sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==} + + '@bundled-es-modules/memfs@4.17.0': + resolution: {integrity: sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==} + + '@bundled-es-modules/postcss-calc-ast-parser@0.1.6': + resolution: {integrity: sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==} + '@capsizecss/unpack@4.0.0': resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==} engines: {node: '>=18'} @@ -892,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==} @@ -1497,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==} @@ -1714,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'} @@ -2155,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} @@ -2561,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'} @@ -2612,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'} @@ -2993,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'} @@ -3191,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'} @@ -3258,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'} @@ -3273,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'} @@ -3314,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'} @@ -3330,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'} @@ -3338,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'} @@ -3375,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'} @@ -3408,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'} @@ -3424,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'} @@ -3486,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'} @@ -3501,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'} @@ -3521,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==} @@ -3718,6 +4000,9 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} @@ -3758,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'} @@ -3964,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==} @@ -3982,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'} @@ -4130,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==} @@ -4255,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'} @@ -4343,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'} @@ -4365,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'} @@ -4382,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'} @@ -4428,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'} @@ -4472,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'} @@ -4692,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'} @@ -4718,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==} @@ -4774,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'} @@ -4804,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} @@ -4840,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'} @@ -4849,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'} @@ -4919,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'} @@ -4933,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==} @@ -5012,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'} @@ -5029,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==} @@ -5039,6 +5417,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + klaw-sync@6.0.0: + resolution: {integrity: sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -5565,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'} @@ -5596,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 @@ -5646,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'} @@ -5658,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==} @@ -5707,6 +6107,11 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + patch-package@8.0.1: + resolution: {integrity: sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==} + engines: {node: '>=14', npm: '>5'} + hasBin: true + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -5721,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==} @@ -5728,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==} @@ -5782,6 +6197,10 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-calc-ast-parser@0.1.4: + resolution: {integrity: sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==} + engines: {node: '>=6.5'} + postcss-nested@6.2.0: resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} engines: {node: '>=12.0'} @@ -5796,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} @@ -5804,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'} @@ -5867,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'} @@ -5901,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: @@ -5998,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'} @@ -6103,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==} @@ -6117,6 +6560,9 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -6164,6 +6610,19 @@ packages: resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} engines: {node: '>= 0.8.15'} + 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'} @@ -6176,6 +6635,10 @@ packages: rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -6312,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'} @@ -6404,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'} @@ -6415,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'} @@ -6438,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==} @@ -6468,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==} @@ -6576,6 +7058,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -6606,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'} @@ -6878,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'} @@ -6930,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==} @@ -6978,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: @@ -7245,7 +7757,11 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@9.0.0: + 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'} @@ -7264,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'} @@ -7440,6 +7960,45 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 + '@ast-grep/napi-darwin-arm64@0.36.3': + optional: true + + '@ast-grep/napi-darwin-x64@0.36.3': + optional: true + + '@ast-grep/napi-linux-arm64-gnu@0.36.3': + optional: true + + '@ast-grep/napi-linux-arm64-musl@0.36.3': + optional: true + + '@ast-grep/napi-linux-x64-gnu@0.36.3': + optional: true + + '@ast-grep/napi-linux-x64-musl@0.36.3': + optional: true + + '@ast-grep/napi-win32-arm64-msvc@0.36.3': + optional: true + + '@ast-grep/napi-win32-ia32-msvc@0.36.3': + optional: true + + '@ast-grep/napi-win32-x64-msvc@0.36.3': + optional: true + + '@ast-grep/napi@0.36.3': + optionalDependencies: + '@ast-grep/napi-darwin-arm64': 0.36.3 + '@ast-grep/napi-darwin-x64': 0.36.3 + '@ast-grep/napi-linux-arm64-gnu': 0.36.3 + '@ast-grep/napi-linux-arm64-musl': 0.36.3 + '@ast-grep/napi-linux-x64-gnu': 0.36.3 + '@ast-grep/napi-linux-x64-musl': 0.36.3 + '@ast-grep/napi-win32-arm64-msvc': 0.36.3 + '@ast-grep/napi-win32-ia32-msvc': 0.36.3 + '@ast-grep/napi-win32-x64-msvc': 0.36.3 + '@astrojs/check@0.9.6(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.6.3)': dependencies: '@astrojs/language-server': 2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.6.3) @@ -7689,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 @@ -7717,6 +8278,62 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@base-ui/react@1.4.1(@date-fns/tz@1.5.0)(@types/react@19.1.8)(date-fns@4.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@base-ui/utils': 0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/react-dom': 2.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@floating-ui/utils': 0.2.11 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + optionalDependencies: + '@date-fns/tz': 1.5.0 + '@types/react': 19.1.8 + date-fns: 4.1.0 + + '@base-ui/utils@0.2.8(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.29.7 + '@floating-ui/utils': 0.2.11 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + reselect: 5.2.0 + use-sync-external-store: 1.6.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + + '@bundled-es-modules/deepmerge@4.3.2': + dependencies: + deepmerge: 4.3.1 + + '@bundled-es-modules/glob@10.4.2': + dependencies: + buffer: 6.0.3 + events: 3.3.0 + glob: 10.5.0 + patch-package: 8.0.1 + path: 0.12.7 + stream: 0.0.3 + string_decoder: 1.3.0 + url: 0.11.4 + + '@bundled-es-modules/memfs@4.17.0(tslib@2.8.1)': + dependencies: + assert: 2.1.0 + buffer: 6.0.3 + events: 3.3.0 + memfs: 4.57.2(tslib@2.8.1) + path: 0.12.7 + stream: 0.0.3 + util: 0.12.5 + transitivePeerDependencies: + - tslib + + '@bundled-es-modules/postcss-calc-ast-parser@0.1.6': + dependencies: + postcss-calc-ast-parser: 0.1.4 + '@capsizecss/unpack@4.0.0': dependencies: fontkitten: 1.0.2 @@ -8004,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': @@ -8450,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) @@ -8469,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': {} @@ -8647,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 @@ -9129,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': {} @@ -9508,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 @@ -9607,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 @@ -10021,6 +10683,19 @@ snapshots: optionalDependencies: typescript: 5.6.3 + '@vue/language-core@2.2.0(typescript@5.9.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.9.3 + '@vue/shared@3.5.33': {} '@webassemblyjs/ast@1.14.1': @@ -10126,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 @@ -10324,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: {} @@ -10492,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: {} @@ -10506,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 @@ -10569,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: {} @@ -10580,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 @@ -10592,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: {} @@ -10631,6 +11341,8 @@ snapshots: chance@1.1.9: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -10670,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 @@ -10682,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: {} @@ -10739,6 +11465,8 @@ snapshots: colorette@2.0.20: {} + colorjs.io@0.5.2: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -10749,6 +11477,8 @@ snapshots: commander@11.1.0: {} + commander@12.1.0: {} + commander@13.1.0: {} commander@2.20.3: {} @@ -10764,6 +11494,8 @@ snapshots: compare-versions@6.1.1: {} + component-emitter@2.0.0: {} + concat-map@0.0.1: {} concurrently@9.1.2: @@ -10976,6 +11708,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 + date-fns-jalali@4.1.0-0: {} + date-fns@4.1.0: {} dayjs@1.11.13: {} @@ -11000,12 +11734,23 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -11116,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: {} @@ -11131,6 +11878,8 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} encoding-sniffer@0.2.1: @@ -11406,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 @@ -11428,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 @@ -11585,6 +12360,8 @@ snapshots: expect-type@1.1.0: {} + expr-eval-fork@2.0.2: {} + express@5.1.0: dependencies: accepts: 2.0.0 @@ -11713,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 @@ -11734,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 @@ -11748,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 @@ -11803,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: @@ -11848,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 @@ -12182,12 +12996,6 @@ snapshots: dependencies: '@babel/runtime': 7.27.0 - i18next@24.2.3(typescript@5.6.3): - dependencies: - '@babel/runtime': 7.27.0 - optionalDependencies: - typescript: 5.6.3 - i18next@24.2.3(typescript@5.9.3): dependencies: '@babel/runtime': 7.27.0 @@ -12202,6 +13010,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} immer@10.1.1: {} @@ -12219,6 +13029,8 @@ snapshots: indent-string@5.0.0: {} + inherits@2.0.3: {} + inherits@2.0.4: {} ini@6.0.0: {} @@ -12273,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 @@ -12303,6 +13119,8 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: {} + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -12332,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 @@ -12403,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 @@ -12420,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: @@ -12521,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: {} @@ -12537,6 +13377,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonify@0.0.1: {} + jspdf@3.0.3: dependencies: '@babel/runtime': 7.27.0 @@ -12559,6 +13401,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + klaw-sync@6.0.0: + dependencies: + graceful-fs: 4.2.11 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -13364,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 @@ -13406,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: @@ -13476,6 +14343,8 @@ snapshots: p-map@2.1.0: {} + p-map@7.0.4: {} + p-queue@8.1.1: dependencies: eventemitter3: 5.0.1 @@ -13485,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 @@ -13557,6 +14428,23 @@ snapshots: parseurl@1.3.3: {} + patch-package@8.0.1: + dependencies: + '@yarnpkg/lockfile': 1.1.0 + chalk: 4.1.2 + ci-info: 3.9.0 + cross-spawn: 7.0.6 + find-yarn-workspace-root: 2.0.0 + fs-extra: 10.1.0 + json-stable-stringify: 1.3.0 + klaw-sync: 6.0.0 + minimist: 1.2.8 + open: 7.4.2 + semver: 7.7.4 + slash: 2.0.0 + tmp: 0.2.7 + yaml: 2.8.4 + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -13565,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: {} @@ -13608,6 +14508,10 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss-calc-ast-parser@0.1.4: + dependencies: + postcss-value-parser: 3.3.1 + postcss-nested@6.2.0(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -13623,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 @@ -13631,6 +14537,8 @@ snapshots: postgres@3.4.9: {} + powershell-utils@0.1.0: {} + prelude-ls@1.1.2: {} prelude-ls@1.2.1: {} @@ -13699,6 +14607,8 @@ snapshots: punycode.js@2.3.1: {} + punycode@1.4.1: {} + punycode@2.3.1: {} qs@6.14.0: @@ -13735,19 +14645,18 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(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 - scheduler: 0.26.0 - react-i18next@15.4.1(i18next@24.2.3(typescript@5.6.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-dom@19.1.0(react@19.1.0): dependencies: - '@babel/runtime': 7.27.0 - html-parse-stringify: 3.0.1 - i18next: 24.2.3(typescript@5.6.3) react: 19.1.0 - optionalDependencies: - react-dom: 19.1.0(react@19.1.0) + scheduler: 0.26.0 react-i18next@15.4.1(i18next@24.2.3(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: @@ -13833,6 +14742,10 @@ 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: {} @@ -14021,6 +14934,8 @@ snapshots: dependencies: type-fest: 4.33.0 + remeda@2.39.0: {} + request-light@0.5.8: {} request-light@0.7.0: {} @@ -14029,6 +14944,8 @@ snapshots: require-from-string@2.0.2: {} + reselect@5.2.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -14084,6 +15001,15 @@ snapshots: rgbcolor@1.0.1: optional: true + rollup-plugin-visualizer@7.0.1(rollup@4.57.1): + dependencies: + open: 11.0.0 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 18.0.0 + optionalDependencies: + rollup: 4.57.1 + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 @@ -14127,6 +15053,8 @@ snapshots: rrweb-cssom@0.8.0: {} + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -14328,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: @@ -14407,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: {} @@ -14417,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 @@ -14467,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 @@ -14494,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 @@ -14601,6 +15563,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.2: {} tinyexec@1.0.2: {} @@ -14622,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 @@ -14865,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 @@ -14903,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 @@ -14981,6 +15960,41 @@ snapshots: - 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) + '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: + vite: 6.4.1(@types/node@22.12.0)(jiti@2.6.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.4) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + 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) @@ -15286,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 @@ -15296,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: {} @@ -15394,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`