@viax.io/uxm 4.40.1 → 4.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +78 -19
  3. package/dist/studio/index.cjs.map +1 -1
  4. package/dist/studio/index.d.ts +3 -2
  5. package/dist/studio/index.d.ts.map +1 -1
  6. package/dist/studio/index.js.map +1 -1
  7. package/dist/studio/lib/registry/composite/list-item.cjs +22 -5
  8. package/dist/studio/lib/registry/composite/list-item.cjs.map +1 -1
  9. package/dist/studio/lib/registry/composite/list-item.d.ts.map +1 -1
  10. package/dist/studio/lib/registry/composite/list-item.js +22 -5
  11. package/dist/studio/lib/registry/composite/list-item.js.map +1 -1
  12. package/dist/ui/app-sidebar/app-sidebar.css +0 -1
  13. package/dist/ui/app-top-bar/app-top-bar.css +0 -1
  14. package/dist/ui/avatar/avatar.css +0 -1
  15. package/dist/ui/back-link/back-link.css +0 -1
  16. package/dist/ui/badge/badge.css +0 -1
  17. package/dist/ui/banner/banner.css +0 -1
  18. package/dist/ui/breadcrumb/breadcrumb.css +0 -1
  19. package/dist/ui/bulk-action-bar/bulk-action-bar.css +0 -1
  20. package/dist/ui/button/button.css +0 -1
  21. package/dist/ui/button-group/button-group.css +0 -1
  22. package/dist/ui/button-icon/button-icon.css +0 -1
  23. package/dist/ui/button-with-icon/button-with-icon.css +0 -1
  24. package/dist/ui/calendar/calendar.css +0 -1
  25. package/dist/ui/card/card.css +0 -1
  26. package/dist/ui/checkbox/checkbox.css +0 -1
  27. package/dist/ui/chip/chip.css +0 -1
  28. package/dist/ui/cluster/cluster.css +0 -1
  29. package/dist/ui/code-editor/code-editor.css +0 -1
  30. package/dist/ui/color-input/color-input.css +0 -1
  31. package/dist/ui/component-row/component-row.css +0 -1
  32. package/dist/ui/config-component-row/config-component-row.css +0 -1
  33. package/dist/ui/config-segment-item/config-segment-item.css +0 -1
  34. package/dist/ui/currency-input/currency-input.css +0 -1
  35. package/dist/ui/data-table/data-table.css +0 -1
  36. package/dist/ui/date-input/date-input.css +0 -1
  37. package/dist/ui/detail-section/detail-section.css +0 -1
  38. package/dist/ui/dialog/dialog.css +0 -1
  39. package/dist/ui/disclosure/disclosure.css +0 -1
  40. package/dist/ui/divider/divider.css +0 -1
  41. package/dist/ui/editable-cell/editable-cell.css +0 -1
  42. package/dist/ui/empty-state/empty-state.css +0 -1
  43. package/dist/ui/error-page/error-page.css +0 -1
  44. package/dist/ui/explorer-list-item/explorer-list-item.css +0 -1
  45. package/dist/ui/explorer-section/explorer-section.css +0 -1
  46. package/dist/ui/file-upload/file-upload.css +0 -1
  47. package/dist/ui/filter-tabs/filter-tabs.css +0 -1
  48. package/dist/ui/form-field/form-field.css +0 -1
  49. package/dist/ui/icon/icon.css +0 -1
  50. package/dist/ui/icon-button/icon-button.css +0 -1
  51. package/dist/ui/icon-tile/icon-tile.css +0 -1
  52. package/dist/ui/inline-action/inline-action.css +0 -1
  53. package/dist/ui/inline-filter/inline-filter.css +0 -1
  54. package/dist/ui/input/input.css +0 -1
  55. package/dist/ui/input-with-icon/input-with-icon.css +0 -1
  56. package/dist/ui/language-switcher/language-switcher.css +0 -1
  57. package/dist/ui/lifecycle-connector/lifecycle-connector.css +0 -1
  58. package/dist/ui/lifecycle-drop-slot/lifecycle-drop-slot.css +0 -1
  59. package/dist/ui/lifecycle-edge-label/lifecycle-edge-label.css +0 -1
  60. package/dist/ui/lifecycle-group-box/lifecycle-group-box.css +0 -1
  61. package/dist/ui/lifecycle-minimap/lifecycle-minimap.css +0 -1
  62. package/dist/ui/lifecycle-node-card/lifecycle-node-card.css +0 -1
  63. package/dist/ui/lifecycle-terminal/lifecycle-terminal.css +0 -1
  64. package/dist/ui/lifecycle-zoom-control/lifecycle-zoom-control.css +0 -1
  65. package/dist/ui/link/link.css +0 -1
  66. package/dist/ui/list/list-item-preview.cjs +9 -4
  67. package/dist/ui/list/list-item-preview.cjs.map +1 -1
  68. package/dist/ui/list/list-item-preview.d.ts.map +1 -1
  69. package/dist/ui/list/list-item-preview.js +10 -5
  70. package/dist/ui/list/list-item-preview.js.map +1 -1
  71. package/dist/ui/list/list.cjs +9 -1
  72. package/dist/ui/list/list.cjs.map +1 -1
  73. package/dist/ui/list/list.css +39 -1
  74. package/dist/ui/list/list.d.ts +32 -1
  75. package/dist/ui/list/list.d.ts.map +1 -1
  76. package/dist/ui/list/list.js +8 -1
  77. package/dist/ui/list/list.js.map +1 -1
  78. package/dist/ui/listbox/listbox.css +0 -1
  79. package/dist/ui/loader/loader.css +0 -1
  80. package/dist/ui/menu/menu.css +0 -1
  81. package/dist/ui/meta-row/meta-row.css +0 -1
  82. package/dist/ui/modal/modal.css +0 -1
  83. package/dist/ui/number-input/number-input.css +0 -1
  84. package/dist/ui/number-stepper/number-stepper.css +0 -1
  85. package/dist/ui/option-list/option-list.css +0 -1
  86. package/dist/ui/page-header/page-header.css +0 -1
  87. package/dist/ui/page-shell/page-shell.css +0 -1
  88. package/dist/ui/password-input/password-input.css +0 -1
  89. package/dist/ui/phone-input/phone-input.css +0 -1
  90. package/dist/ui/pill-select/pill-select.css +0 -1
  91. package/dist/ui/popover/popover.css +0 -1
  92. package/dist/ui/progress-bar/progress-bar.css +0 -1
  93. package/dist/ui/property-field/property-field.css +0 -1
  94. package/dist/ui/radio-group/radio-group.css +0 -1
  95. package/dist/ui/range-slider/range-slider.css +0 -1
  96. package/dist/ui/responsive-grid/responsive-grid.css +0 -1
  97. package/dist/ui/search-dropdown/search-dropdown.css +0 -1
  98. package/dist/ui/section-header/section-header.css +0 -1
  99. package/dist/ui/segment-card/segment-card.css +0 -1
  100. package/dist/ui/segment-row/segment-row.css +0 -1
  101. package/dist/ui/side-flexpane/side-flexpane.css +0 -1
  102. package/dist/ui/sidebar-nav-item/sidebar-nav-item.css +0 -1
  103. package/dist/ui/sidebar-nav-trigger/sidebar-nav-trigger.css +0 -1
  104. package/dist/ui/slider/slider.css +0 -1
  105. package/dist/ui/stack/stack.css +0 -1
  106. package/dist/ui/stat-card/stat-card.css +0 -1
  107. package/dist/ui/tabs/tabs.css +0 -1
  108. package/dist/ui/tabs-underline/tabs-underline.css +0 -1
  109. package/dist/ui/tag/tag.css +0 -1
  110. package/dist/ui/thumbnail/thumbnail.css +0 -1
  111. package/dist/ui/time-input/time-input.css +0 -1
  112. package/dist/ui/timeline-entry/timeline-entry.css +0 -1
  113. package/dist/ui/toast/toast.css +0 -1
  114. package/dist/ui/toggle-switch/toggle-switch.css +0 -1
  115. package/dist/ui/tooltip/tooltip.css +0 -1
  116. package/dist/ui/type-overview-card/type-overview-card.css +0 -1
  117. package/dist/ui/view-switcher/view-switcher.css +0 -1
  118. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [4.41.0](https://github.com/viax-io/uxm/compare/v4.40.2...v4.41.0) (2026-09-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * **list:** add a media leading slot to ListItem ([f346cd4](https://github.com/viax-io/uxm/commit/f346cd43acf89df9c194d733ffe0b218cfdb1f51))
7
+
8
+ ## [4.40.2](https://github.com/viax-io/uxm/compare/v4.40.1...v4.40.2) (2026-09-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **build:** stop Sass emitting [@charset](https://github.com/charset) into every component CSS fragment ([cb7c22b](https://github.com/viax-io/uxm/commit/cb7c22b0f9dd44d2c140cd0e5da68bf8c149ce5b))
14
+
1
15
  ## [4.40.1](https://github.com/viax-io/uxm/compare/v4.40.0...v4.40.1) (2026-09-08)
2
16
 
3
17
 
package/README.md CHANGED
@@ -11,16 +11,31 @@ The package ships **no `"use client"` / `"use server"` directives** by design: c
11
11
  - **UI primitives** (`@viax.io/uxm/ui`) — BEM-classed React 19 components.
12
12
  - **Icon registry** (`@viax.io/uxm/ui`) — `ICONS`, `ICON_OPTIONS`, `getIcon`, `IconDef` for tooling that enumerates the bundled icon set.
13
13
  - **Design tokens** (`@viax.io/uxm/tokens`) — the canonical `themeTokens` array plus helpers (`findToken`, `resolveHex`, `isTokenValue`) and the `ThemeToken` type.
14
+ - **Behaviour hooks** (`@viax.io/uxm/hooks`) — `useDismiss`, `useFocusTrap`, `useFocusOnMount`, `useRovingTabIndex`, `useScrollLock`, `usePortal`, `useToastStore` for hosts composing their own floating layers or keyboard widgets.
14
15
  - **Themable previews** (`@viax.io/uxm/previews`) — preview components (one per atom, plus composite previews) used by host shells like MODO's brand-settings editor to render live, knob-driven theme exploration.
16
+ - **Studio** (`@viax.io/uxm/studio`) — `UxmApp`, the full design workbench MODO serves at `/uxm`, backend-decoupled through the `StudioPersistence` contract; see [Studio](#studio).
15
17
  - **WCAG / contrast helpers** (`@viax.io/uxm`) — `contrastRatio`, `parseColor`, `rgbToHex`, `suggestAccessibleColor`, `suggestAccessibleToken`, `wcagLevel`, plus `RGB` and `TokenCandidate` types.
16
18
  - **Default stylesheets** — `@viax.io/uxm/ui.css` (component primitive defaults) and `@viax.io/uxm/tokens.css` (token declarations).
17
19
 
18
20
  ## Install
19
21
 
20
22
  ```bash
21
- npm install @viax.io/uxm
23
+ npm i @viax.io/uxm
22
24
  ```
23
25
 
26
+ Every example in this README imports from `@viax.io/uxm` and its subpaths.
27
+
28
+ > **Tip: install under an alias.** viax apps install the package as `uxm` so the real name
29
+ > appears once, in `package.json`, and imports stay short and rename-proof:
30
+ >
31
+ > ```bash
32
+ > npm i uxm@npm:@viax.io/uxm@latest
33
+ > ```
34
+ >
35
+ > That writes `"uxm": "npm:@viax.io/uxm@^4.40.1"` and lets you write `import { Button } from 'uxm/ui'`
36
+ > or `import 'uxm/tokens.css'`. The subpath exports are identical under either name — read `@viax.io/uxm`
37
+ > as `uxm` throughout.
38
+
24
39
  Peer deps: `react@^19`, `react-dom@^19`. Node `>=20` for local dev.
25
40
 
26
41
  Published publicly on npm — no registry configuration, credentials or VPN required.
@@ -75,6 +90,9 @@ import { ButtonPrimary, themeTokens } from '@viax.io/uxm';
75
90
  | `@viax.io/uxm/tokens.css` | `--color-*` declarations on `:root`. |
76
91
  | `@viax.io/uxm/hooks` | The behaviour hooks the atoms are built on — `useDismiss`, `useFocusTrap`, `useFocusOnMount`, `useRovingTabIndex`, `useScrollLock`, `usePortal`, `useToastStore` — for hosts composing their own floating layers or keyboard widgets. Pure React, no atom imports. |
77
92
  | `@viax.io/uxm/previews` | Preview components for host shells building theme editors. **No preview symbol leaks into `/ui`** — see the tree-shake guarantee below. |
93
+ | `@viax.io/uxm/studio` | `UxmApp` + `UxmProvider` / `useUxm`, the component `registry`, the `StudioPersistence` contract and its three adapters (`createHttpPersistence`, `createClientPersistence`, `createReadOnlyPersistence`). |
94
+ | `@viax.io/uxm/studio/generate-css` | `generateOverridesCss` + the CSS sanitizers — turn saved studio overrides and a `BrandConfig` into a stylesheet on the server, without pulling in the workbench UI. |
95
+ | `@viax.io/uxm/studio.css` | Tailwind utilities for the studio shell + token declarations. Does **not** bundle the atom CSS — a studio host imports `ui.css` alongside it. |
78
96
 
79
97
  ## Component catalog
80
98
 
@@ -83,13 +101,13 @@ Every component folder ships a `README.md` documenting props, CSS variables, MOD
83
101
  > The catalog below covers every folder in `src/ui/`. If you add a component, add it here too — and keep `src/ui/` the authoritative source if the two ever drift.
84
102
 
85
103
  ### Forms & inputs
86
- [`calendar`](src/ui/calendar/README.md) · [`checkbox`](src/ui/checkbox/README.md) · [`color-input`](src/ui/color-input/README.md) (ColorInput + ColorInputPopover) · [`currency-input`](src/ui/currency-input/README.md) · [`date-input`](src/ui/date-input/README.md) · [`editable-cell`](src/ui/editable-cell/README.md) · [`field-error`](src/ui/field-error/README.md) · [`file-upload`](src/ui/file-upload/README.md) · [`form-field`](src/ui/form-field/README.md) · [`input`](src/ui/input/README.md) (TextInput + Select + Textarea) · [`input-with-icon`](src/ui/input-with-icon/README.md) · [`number-input`](src/ui/number-input/README.md) · [`number-stepper`](src/ui/number-stepper/README.md) · [`password-input`](src/ui/password-input/README.md) · [`phone-input`](src/ui/phone-input/README.md) · [`pill-select`](src/ui/pill-select/README.md) · [`radio-group`](src/ui/radio-group/README.md) · [`range-slider`](src/ui/range-slider/README.md) · [`search-dropdown`](src/ui/search-dropdown/README.md) · [`slider`](src/ui/slider/README.md) · [`time-input`](src/ui/time-input/README.md) · [`toggle-switch`](src/ui/toggle-switch/README.md)
104
+ [`calendar`](src/ui/calendar/README.md) · [`checkbox`](src/ui/checkbox/README.md) · [`code-editor`](src/ui/code-editor/README.md) (CodeEditor + CodeBlock) · [`color-input`](src/ui/color-input/README.md) (ColorInput + ColorInputPopover) · [`currency-input`](src/ui/currency-input/README.md) · [`date-input`](src/ui/date-input/README.md) · [`editable-cell`](src/ui/editable-cell/README.md) · [`field-error`](src/ui/field-error/README.md) · [`file-upload`](src/ui/file-upload/README.md) · [`form-field`](src/ui/form-field/README.md) · [`input`](src/ui/input/README.md) (TextInput + Select + Textarea) · [`input-with-icon`](src/ui/input-with-icon/README.md) · [`number-input`](src/ui/number-input/README.md) · [`number-stepper`](src/ui/number-stepper/README.md) · [`password-input`](src/ui/password-input/README.md) · [`phone-input`](src/ui/phone-input/README.md) · [`pill-select`](src/ui/pill-select/README.md) · [`radio-group`](src/ui/radio-group/README.md) · [`range-slider`](src/ui/range-slider/README.md) · [`search-dropdown`](src/ui/search-dropdown/README.md) · [`slider`](src/ui/slider/README.md) · [`time-input`](src/ui/time-input/README.md) · [`toggle-switch`](src/ui/toggle-switch/README.md)
87
105
 
88
106
  ### Buttons & actions
89
107
  [`back-link`](src/ui/back-link/README.md) · [`bulk-action-bar`](src/ui/bulk-action-bar/README.md) · [`button`](src/ui/button/README.md) (Primary/Secondary/Tertiary/Ghost) · [`button-group`](src/ui/button-group/README.md) · [`button-icon`](src/ui/button-icon/README.md) (deprecated → `icon-button` `variant="filled"`) · [`button-with-icon`](src/ui/button-with-icon/README.md) · [`icon-button`](src/ui/icon-button/README.md) · [`inline-action`](src/ui/inline-action/README.md) · [`link`](src/ui/link/README.md)
90
108
 
91
109
  ### Navigation
92
- [`app-sidebar`](src/ui/app-sidebar/README.md) · [`app-top-bar`](src/ui/app-top-bar/README.md) · [`breadcrumb`](src/ui/breadcrumb/README.md) · [`filter-tabs`](src/ui/filter-tabs/README.md) · [`menu`](src/ui/menu/README.md) · [`sidebar-nav-item`](src/ui/sidebar-nav-item/README.md) · [`tabs`](src/ui/tabs/README.md) · [`tabs-underline`](src/ui/tabs-underline/README.md) · [`view-switcher`](src/ui/view-switcher/README.md)
110
+ [`app-sidebar`](src/ui/app-sidebar/README.md) · [`app-top-bar`](src/ui/app-top-bar/README.md) · [`breadcrumb`](src/ui/breadcrumb/README.md) · [`filter-tabs`](src/ui/filter-tabs/README.md) · [`menu`](src/ui/menu/README.md) · [`sidebar-nav-item`](src/ui/sidebar-nav-item/README.md) · [`sidebar-nav-trigger`](src/ui/sidebar-nav-trigger/README.md) · [`tabs`](src/ui/tabs/README.md) · [`tabs-underline`](src/ui/tabs-underline/README.md) · [`view-switcher`](src/ui/view-switcher/README.md)
93
111
 
94
112
  ### Overlays & floating layers
95
113
  [`dialog`](src/ui/dialog/README.md) · [`hover-tooltip`](src/ui/hover-tooltip/README.md) · [`listbox`](src/ui/listbox/README.md) (Listbox + MultiListbox) · [`modal`](src/ui/modal/README.md) · [`option-list`](src/ui/option-list/README.md) · [`popover`](src/ui/popover/README.md) · [`toast`](src/ui/toast/README.md) (Toast + Toaster + the `toast.*` API)
@@ -125,6 +143,9 @@ The canonical token catalogue lives in `src/tokens/index.ts` as the `themeTokens
125
143
  - `cssVar` — the `--color-*` identifier consumed by components
126
144
  - `hex` / `darkHex` — light/dark defaults
127
145
  - `group` — one of `surfaces` · `text` · `borders` · `accent` · `highlights` · `categories` · `semantic`
146
+ - `identity` (optional) — promotes the token into the brand editor's Identity section as a top-level knob; reserved for tokens that fan out (the accent base drives the whole ramp)
147
+
148
+ `tokens.css` and `themeTokens` must stay in lockstep — `npm run check:drift` fails when a `--color-*` variable exists on one side only.
128
149
 
129
150
  ```ts
130
151
  import { themeTokens, findToken } from '@viax.io/uxm/tokens';
@@ -195,47 +216,79 @@ import { ButtonPreview, type PreviewShellContext } from '@viax.io/uxm/previews';
195
216
  />;
196
217
  ```
197
218
 
198
- **Tree-shake guarantee**: no preview symbols leak into `@viax.io/uxm/ui`, so consumers that only import primitives never pay for preview code. This is **upheld by convention, not enforced by the build** nothing fails if a barrel breaks it. When you touch `src/ui/index.ts` or any `src/ui/*/index.ts`, check by hand that no `*-preview` module is re-exported; after a build, `grep "Preview" dist/ui/index.js` should return nothing.
219
+ **Tree-shake guarantee**: no preview symbols leak into `@viax.io/uxm/ui`, so consumers that only import primitives never pay for preview code. **ESLint enforces it** (`eslint.config.mjs`): `no-restricted-imports` rejects any `*-preview` re-export from `src/ui/index.ts` or a `src/ui/*/index.ts`, and the `import/no-restricted-paths` zones pin the layer direction — `ui` may not import `studio` or the previews barrel, `tokens` imports nothing, and `lib` / `hooks` / `helpers` may not import components. After a build, `grep "Preview" dist/ui/index.js` still returns nothing.
220
+
221
+ ## Studio
222
+
223
+ `@viax.io/uxm/studio` ships `UxmApp`, the design workbench MODO serves at `/uxm`: sidebar, canvas, properties panel and WCAG panel over the same previews. It is decoupled from any backend through the `StudioPersistence` contract — pick an adapter per host:
224
+
225
+ - `createHttpPersistence()` — reads and writes against a Hono API.
226
+ - `createClientPersistence()` — live preview plus client-side asset uploads (what the `portal/` dev shell uses).
227
+ - `createReadOnlyPersistence()` — a static, view-only portal.
228
+
229
+ ```tsx
230
+ import '@viax.io/uxm/tokens.css';
231
+ import '@viax.io/uxm/ui.css';
232
+ import '@viax.io/uxm/studio.css';
233
+ import { UxmApp, createHttpPersistence } from '@viax.io/uxm/studio';
234
+
235
+ <UxmApp persistence={createHttpPersistence('/api/uxm')} />;
236
+ ```
237
+
238
+ `@viax.io/uxm/studio/generate-css` exposes `generateOverridesCss` and the CSS sanitizers on their own, so a server can render the saved overrides into a stylesheet without loading the workbench. The studio is the only layer styled with Tailwind; `studio.css` bundles those utilities and the token declarations but not the atom CSS.
199
239
 
200
240
  ## Architecture
201
241
 
202
242
  ```
203
243
  ┌─────────────────────────────────────────────────┐
204
- │ @viax.io/uxm/previews │ ← host editors (MODO brand-settings)
244
+ │ @viax.io/uxm/studio (+ studio.css) │ ← the design workbench (MODO /uxm)
245
+ │ UxmApp, registry, StudioPersistence adapters │
246
+ └────────────────┬────────────────────────────────┘
247
+ │ renders
248
+
249
+ ┌─────────────────────────────────────────────────┐
250
+ │ @viax.io/uxm/previews │ ← host editors (MODO brand-settings)
205
251
  │ themable, shell-aware preview components │
206
252
  └────────────────┬────────────────────────────────┘
207
253
  │ consumes
208
254
 
209
255
  ┌─────────────────────────────────────────────────┐
210
- │ @viax.io/uxm/ui │ ← application code
256
+ │ @viax.io/uxm/ui (+ ui.css) │ ← application code
211
257
  │ BEM-classed React components │
212
258
  │ + Icon registry (ICONS, getIcon, …) │
259
+ │ built on @viax.io/uxm/hooks │
213
260
  └────────────────┬────────────────────────────────┘
214
261
  │ reads fallbacks
215
262
 
216
263
  ┌─────────────────────────────────────────────────┐
217
- │ @viax.io/uxm/tokens (+ tokens.css) │ ← MODO brand-settings edits this
264
+ │ @viax.io/uxm/tokens (+ tokens.css) │ ← MODO brand-settings edits this
218
265
  │ themeTokens array + --color-* declarations │
219
266
  └─────────────────────────────────────────────────┘
220
267
  ```
221
268
 
222
- Every layer is independently importable; every layer has its own type declarations and own CSS bundle.
269
+ The dependency direction is `studio / previews → ui → tokens`, enforced by the ESLint layer zones. Every layer is independently importable; every layer has its own type declarations and own CSS bundle.
223
270
 
224
271
  ## Build & develop
225
272
 
226
273
  ```bash
227
274
  npm install
228
- npm run dev:modo # Vite dev server for the studio portal — fastest loop, no build needed
229
- npm run build # full dist/ (see the pipeline below)
230
- npm run typecheck # tsc --noEmit for src + portal
231
- npm run lint # eslint .
232
- npm run lint:fix # eslint . --fix
233
- npm run dev # tsup --watch — only for local linked dev against a consumer
275
+ npm run dev:modo # Vite dev server for the studio portal — fastest loop, no build needed
276
+ npm run build:modo # Vite production build of the portal
277
+ npm run build # full dist/ (see the pipeline below)
278
+ npm run typecheck # tsc --noEmit for src + portal
279
+ npm run lint # eslint .
280
+ npm run lint:fix # eslint . --fix
281
+ npm run check:drift # state-var drift, type-scale and tokens.css ↔ themeTokens parity gates
282
+ npm test # Vitest smoke suite (tests/), jsdom — `npm run test:watch` while iterating
283
+ npm run test:coverage # coverage summary for orientation only — no thresholds, not a gate
284
+ npm run dev # tsup --watch — only for local linked dev against a consumer
234
285
  ```
235
286
 
236
287
  **`npm run dev:modo` is the fastest way to see a UI change.** The portal pulls `src/ui/**/*.scss` through `import.meta.glob`, so components render with real CSS over HMR without building `dist/` first.
237
288
 
238
- **There are no tests.** `test:ci` and `test:coverage` are intentional no-op stubs, so "tests pass" is never a meaningful verification here — use `typecheck`, `lint`, `build`, or the portal.
289
+ **Tests are a small Vitest smoke suite, not coverage.** `npm test` (`tests/*.test.tsx`, jsdom + Testing Library + axe-core) pins the contracts nothing else can see: focus trap / Escape / outside-click on the floating layers, ARIA wiring on the pickers, the CSS sanitizers and the overrides generator, and an axe pass over a form and a dialog. It also pins the public surface: the export names of every subpath, every `--uxm-*` variable the stylesheets read, and that `dist/` loads as ESM and CJS (snapshots in `tests/__snapshots__/`; a minus line in the diff is a removed export or knob, i.e. a MAJOR). See `tests/README.md`. Add a test when you touch one of those contracts or fix a behavioural bug; do not chase coverage on presentational atoms. For a visual change "tests pass" proves nothing the verification is `typecheck`, `lint`, `build` and the portal.
290
+
291
+ CI (`.github/workflows/ci.yml`) runs `lint`, `typecheck`, `check:drift`, `build` and `test:ci` on every pull request, plus an `npm audit` job that fails at critical.
239
292
 
240
293
  `npm run build` chains five steps, and the order is deliberate:
241
294
 
@@ -259,9 +312,15 @@ dist/
259
312
  ├── tokens/
260
313
  │ ├── index.{js,cjs,d.ts}
261
314
  │ └── index.css
262
- └── previews/
315
+ ├── hooks/
316
+ │ └── index.{js,cjs,d.ts}
317
+ ├── previews/
318
+ │ ├── index.{js,cjs,d.ts}
319
+ │ └── …per-preview files…
320
+ └── studio/
263
321
  ├── index.{js,cjs,d.ts}
264
- └── …per-preview files…
322
+ ├── studio.css
323
+ └── persistence/generate-css.{js,cjs,d.ts}
265
324
  ```
266
325
 
267
326
  ## Local linked development (e.g. modo)
@@ -287,9 +346,9 @@ This makes commit types carry semver meaning: `fix` → PATCH, `feat` → MINOR,
287
346
  - **Add a component**: create `src/ui/{name}/` with `{name}.tsx`, `{name}.scss`, `index.ts`. Re-export from **both** the folder `index.ts` and `src/ui/index.ts`. Add the compiled stylesheet to `src/ui/styles.css` as `@import "./{name}/{name}.css";` in cascade order — the aggregator is hand-written, and a component whose `@import` is missing ships with no CSS. Add a `README.md` mirroring [`button/README.md`](src/ui/button/README.md) (simple) or [`data-table/README.md`](src/ui/data-table/README.md) (complex).
288
347
  - **Update the AI skill in the same change**: `skills/viax-uxm/` needs a catalog row, a cheatsheet row, and a bullet under `### Unreleased` in `SKILL.md`. Leave the version and component-count markers alone — CI stamps those at release via `scripts/stamp-skill-version.mjs`.
289
348
  - **Add a preview**: create `{name}-preview.tsx` next to the component — previews live beside their component, not in `src/previews/`. Use `PreviewProps` and project knob values as inline CSS vars so production CSS rules paint them. Never re-export a preview from a `src/ui/` barrel (see the tree-shake guarantee above).
290
- - **Add a token**: add an entry to `themeTokens` in `src/tokens/index.ts` and declare the `--color-*` variable in `src/tokens/index.css`. Reference it from component SCSS via `var(--uxm-foo-bar, var(--color-new-token))` — never hardcode a colour, spacing, or radius.
349
+ - **Add a token**: add an entry to `themeTokens` in `src/tokens/index.ts` and declare the `--color-*` variable in `src/tokens/index.css` — `npm run check:drift` fails if only one side changes. Reference it from component SCSS via `var(--uxm-foo-bar, var(--color-new-token))` — never hardcode a colour, spacing, or radius.
291
350
  - Commits follow conventional-commit format (`commitizen` + `commitlint` enforced via `husky`); `npm run commit` walks you through it. Commit types drive the released version — see [Releases](#releases).
292
- - Run `npm run lint && npm run typecheck && npm test && npm run build` before opening a pull request.
351
+ - Run `npm run lint && npm run typecheck && npm run check:drift && npm test && npm run build` before opening a pull request — the same gates CI runs.
293
352
 
294
353
  ## License
295
354
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/studio/index.ts"],"sourcesContent":["/**\n * @viax.io/uxm/studio — the UXM design workbench (sidebar + canvas + properties\n * panel + previews + editors), the same portal shipped at modo's `/uxm` route.\n *\n * Decoupled from any specific backend via `StudioPersistence`: inject\n * `createHttpPersistence()` to read/write against a Hono API, or\n * `createReadOnlyPersistence()` for a static portal (live-preview only).\n *\n * Styles ship separately as `@viax.io/uxm/studio.css` — import it once in the\n * host; it bundles tokens + UI atom CSS + the Tailwind utilities the shell\n * uses, so no host Tailwind config is required.\n */\nexport { UxmApp } from './uxm-app';\nexport type { UxmAppProps } from './uxm-app';\nexport { UxmProvider, useUxm } from './lib/context';\nexport { registry, getComponentDef, categoryColors } from './lib/registry';\nexport type {\n BrandConfig,\n BrandTokens,\n Category,\n ComponentDef,\n EditorControlType,\n LayoutVariant,\n PreviewProps,\n StyleOverrides,\n StyleProperty,\n} from './lib/types';\n\n// Persistence contract + adapters + the single CSS generator (R5/R7).\nexport {\n createReadOnlyPersistence,\n createClientPersistence,\n createHttpPersistence,\n generateOverridesCss,\n} from './persistence';\nexport type {\n StudioPersistence,\n StudioState,\n AllOverrides,\n} from './persistence';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,qBAAuB;AAEvB,qBAAoC;AACpC,sBAA0D;AAc1D,yBAKO;","names":[]}
1
+ {"version":3,"sources":["../../src/studio/index.ts"],"sourcesContent":["/**\n * @viax.io/uxm/studio — the UXM design workbench (sidebar + canvas + properties\n * panel + previews + editors), the same portal shipped at modo's `/uxm` route.\n *\n * Decoupled from any specific backend via `StudioPersistence`: inject\n * `createHttpPersistence()` to read/write against a Hono API, or\n * `createReadOnlyPersistence()` for a static portal (live-preview only).\n *\n * Styles ship separately as `@viax.io/uxm/studio.css` — import it once in the\n * host next to `tokens.css` + `ui.css`; it bundles the token declarations and\n * the Tailwind utilities the shell uses (NOT the atom CSS, so atoms are never\n * duplicated), so no host Tailwind config is required.\n */\nexport { UxmApp } from './uxm-app';\nexport type { UxmAppProps } from './uxm-app';\nexport { UxmProvider, useUxm } from './lib/context';\nexport { registry, getComponentDef, categoryColors } from './lib/registry';\nexport type {\n BrandConfig,\n BrandTokens,\n Category,\n ComponentDef,\n EditorControlType,\n LayoutVariant,\n PreviewProps,\n StyleOverrides,\n StyleProperty,\n} from './lib/types';\n\n// Persistence contract + adapters + the single CSS generator (R5/R7).\nexport {\n createReadOnlyPersistence,\n createClientPersistence,\n createHttpPersistence,\n generateOverridesCss,\n} from './persistence';\nexport type {\n StudioPersistence,\n StudioState,\n AllOverrides,\n} from './persistence';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,qBAAuB;AAEvB,qBAAoC;AACpC,sBAA0D;AAc1D,yBAKO;","names":[]}
@@ -7,8 +7,9 @@
7
7
  * `createReadOnlyPersistence()` for a static portal (live-preview only).
8
8
  *
9
9
  * Styles ship separately as `@viax.io/uxm/studio.css` — import it once in the
10
- * host; it bundles tokens + UI atom CSS + the Tailwind utilities the shell
11
- * uses, so no host Tailwind config is required.
10
+ * host next to `tokens.css` + `ui.css`; it bundles the token declarations and
11
+ * the Tailwind utilities the shell uses (NOT the atom CSS, so atoms are never
12
+ * duplicated), so no host Tailwind config is required.
12
13
  */
13
14
  export { UxmApp } from './uxm-app.js';
14
15
  export type { UxmAppProps } from './uxm-app.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3E,YAAY,EACV,WAAW,EACX,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,GACb,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/studio/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC3E,YAAY,EACV,WAAW,EACX,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,iBAAiB,EACjB,WAAW,EACX,YAAY,GACb,MAAM,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/studio/index.ts"],"sourcesContent":["/**\n * @viax.io/uxm/studio — the UXM design workbench (sidebar + canvas + properties\n * panel + previews + editors), the same portal shipped at modo's `/uxm` route.\n *\n * Decoupled from any specific backend via `StudioPersistence`: inject\n * `createHttpPersistence()` to read/write against a Hono API, or\n * `createReadOnlyPersistence()` for a static portal (live-preview only).\n *\n * Styles ship separately as `@viax.io/uxm/studio.css` — import it once in the\n * host; it bundles tokens + UI atom CSS + the Tailwind utilities the shell\n * uses, so no host Tailwind config is required.\n */\nexport { UxmApp } from './uxm-app';\nexport type { UxmAppProps } from './uxm-app';\nexport { UxmProvider, useUxm } from './lib/context';\nexport { registry, getComponentDef, categoryColors } from './lib/registry';\nexport type {\n BrandConfig,\n BrandTokens,\n Category,\n ComponentDef,\n EditorControlType,\n LayoutVariant,\n PreviewProps,\n StyleOverrides,\n StyleProperty,\n} from './lib/types';\n\n// Persistence contract + adapters + the single CSS generator (R5/R7).\nexport {\n createReadOnlyPersistence,\n createClientPersistence,\n createHttpPersistence,\n generateOverridesCss,\n} from './persistence';\nexport type {\n StudioPersistence,\n StudioState,\n AllOverrides,\n} from './persistence';\n"],"mappings":"AAYA,SAAS,cAAc;AAEvB,SAAS,aAAa,cAAc;AACpC,SAAS,UAAU,iBAAiB,sBAAsB;AAc1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../../src/studio/index.ts"],"sourcesContent":["/**\n * @viax.io/uxm/studio — the UXM design workbench (sidebar + canvas + properties\n * panel + previews + editors), the same portal shipped at modo's `/uxm` route.\n *\n * Decoupled from any specific backend via `StudioPersistence`: inject\n * `createHttpPersistence()` to read/write against a Hono API, or\n * `createReadOnlyPersistence()` for a static portal (live-preview only).\n *\n * Styles ship separately as `@viax.io/uxm/studio.css` — import it once in the\n * host next to `tokens.css` + `ui.css`; it bundles the token declarations and\n * the Tailwind utilities the shell uses (NOT the atom CSS, so atoms are never\n * duplicated), so no host Tailwind config is required.\n */\nexport { UxmApp } from './uxm-app';\nexport type { UxmAppProps } from './uxm-app';\nexport { UxmProvider, useUxm } from './lib/context';\nexport { registry, getComponentDef, categoryColors } from './lib/registry';\nexport type {\n BrandConfig,\n BrandTokens,\n Category,\n ComponentDef,\n EditorControlType,\n LayoutVariant,\n PreviewProps,\n StyleOverrides,\n StyleProperty,\n} from './lib/types';\n\n// Persistence contract + adapters + the single CSS generator (R5/R7).\nexport {\n createReadOnlyPersistence,\n createClientPersistence,\n createHttpPersistence,\n generateOverridesCss,\n} from './persistence';\nexport type {\n StudioPersistence,\n StudioState,\n AllOverrides,\n} from './persistence';\n"],"mappings":"AAaA,SAAS,cAAc;AAEvB,SAAS,aAAa,cAAc;AACpC,SAAS,UAAU,iBAAiB,sBAAsB;AAc1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -25,7 +25,7 @@ const listItemDef = {
25
25
  id: "list-item",
26
26
  name: "List Item",
27
27
  category: "Composite",
28
- description: "Row with icon, title, and optional trailing slot \u2014 used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing \u2014 trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.",
28
+ description: "Row with a leading slot (accent icon tile, or media rendered as-is for a thumbnail / avatar), title, and optional trailing slot \u2014 used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing \u2014 trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.",
29
29
  styleProperties: [
30
30
  // ── Per-state row colors. Filtered into the "States" section by
31
31
  // the State variant. Default-state knobs (`inactiveBg`,
@@ -70,10 +70,14 @@ const listItemDef = {
70
70
  // glyph inside. Keeps the icon visually distinct from the row bg in
71
71
  // every state (default / hover / active / disabled), so the shared
72
72
  // icon knobs don't need to vary per-state.
73
- { key: "iconBg", label: "Icon Bg", control: "color", defaultValue: "var(--color-accent)", section: "icon" },
74
- { key: "iconColor", label: "Icon Color", control: "color", defaultValue: "var(--color-card)", section: "icon" },
75
- { key: "iconSize", label: "Icon Size", control: "number", defaultValue: 24, min: 16, max: 40, step: 2, unit: "px", section: "icon" },
76
- { key: "iconRadius", label: "Icon Radius", control: "slider", defaultValue: 6, min: 0, max: 16, step: 1, unit: "px", section: "icon" }
73
+ { key: "iconBg", label: "Icon Bg", control: "color", defaultValue: "var(--color-accent)", section: "icon", showWhen: { leading: "icon" } },
74
+ { key: "iconColor", label: "Icon Color", control: "color", defaultValue: "var(--color-card)", section: "icon", showWhen: { leading: "icon" } },
75
+ { key: "iconSize", label: "Icon Size", control: "number", defaultValue: 24, min: 16, max: 40, step: 2, unit: "px", section: "icon", showWhen: { leading: "icon" } },
76
+ { key: "iconRadius", label: "Icon Radius", control: "slider", defaultValue: 6, min: 0, max: 16, step: 1, unit: "px", section: "icon", showWhen: { leading: "icon" } },
77
+ // Media slot is the un-tiled leading alternative: the passed node
78
+ // (Thumbnail / avatar / <img>) brings its own surface and radius, so
79
+ // the row only owns the box it sits in — one size knob, no colours.
80
+ { key: "mediaSize", label: "Media Size", control: "number", defaultValue: 36, min: 24, max: 64, step: 2, unit: "px", section: "media", showWhen: { leading: "media" } }
77
81
  ],
78
82
  layoutVariants: [
79
83
  {
@@ -111,6 +115,19 @@ const listItemDef = {
111
115
  ],
112
116
  defaultValue: "default"
113
117
  },
118
+ {
119
+ key: "leading",
120
+ label: "Leading",
121
+ options: [
122
+ // `icon` → the accent `IconTile` (the long-standing default, so an
123
+ // existing saved theme opens on the same row it was saved against).
124
+ // `media` → the node renders as-is; the preview passes a `Thumbnail`,
125
+ // which is the shape this slot exists for (product / people rows).
126
+ { value: "icon", label: "Icon" },
127
+ { value: "media", label: "Media" }
128
+ ],
129
+ defaultValue: "icon"
130
+ },
114
131
  {
115
132
  key: "value",
116
133
  label: "Value Text",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const listItemDef: ComponentDef = {\n id: 'list-item',\n name: 'List Item',\n category: 'Composite',\n description: \"Row with icon, title, and optional trailing slot — used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing — trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.\",\n styleProperties: [\n // ── Per-state row colors. Filtered into the \"States\" section by\n // the State variant. Default-state knobs (`inactiveBg`,\n // `inactiveText`) gate on `state: \"default\"` only — so the bg /\n // text are editable in both static and interactive modes. The\n // interactive-only state knobs (hover / focus / active)\n // additionally gate on `mode: \"interactive\"` so they hide\n // entirely when the row is rendered as a display-only `<div>`.\n // `disabled` is a visual treatment, not an interaction state, so\n // its knobs are NOT mode-gated.\n { key: 'inactiveBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'default' } },\n { key: 'inactiveText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'default' } },\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'hoverText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'focusText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'activeBg', label: 'Background', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'activeText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'disabled' } },\n { key: 'disabledText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'states', showWhen: { state: 'disabled' } },\n // Focus ring + disabled opacity in their own sections.\n { key: 'focusRing', label: 'Ring Color', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.5, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // ── Container (List wrapper)\n { key: 'borderColor', label: 'Border Color', control: 'color', defaultValue: 'var(--color-border)', section: 'container' },\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 16, step: 1, unit: 'px', section: 'container' },\n // ── Shared sizing (applies to every state)\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 16, min: 8, max: 32, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'gap', label: 'Icon Gap', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 11, max: 18, step: 1, unit: 'px' },\n // ── Sub-elements (shared across every state)\n { key: 'valueColor', label: 'Value Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'value', showWhen: { value: 'shown' } },\n { key: 'valueSize', label: 'Value Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'value', showWhen: { value: 'shown' } },\n // Trailing slot is purely composable — the consumer passes whatever\n // ReactNode they want. The preview demos the canonical pattern per\n // mode (chevron for interactive nav rows, Tag for static status\n // rows), so a single Chevron Color knob covers the interactive\n // example. Other trailing decorations (Tag, Badge, Avatar, etc.)\n // bring their own styling via their respective atoms.\n { key: 'chevronColor', label: 'Chevron Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'trailing', showWhen: { mode: 'interactive' } },\n // High-contrast icon \"stamp\" — bright accent tile with a card-coloured\n // glyph inside. Keeps the icon visually distinct from the row bg in\n // every state (default / hover / active / disabled), so the shared\n // icon knobs don't need to vary per-state.\n { key: 'iconBg', label: 'Icon Bg', control: 'color', defaultValue: 'var(--color-accent)', section: 'icon' },\n { key: 'iconColor', label: 'Icon Color', control: 'color', defaultValue: 'var(--color-card)', section: 'icon' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 24, min: 16, max: 40, step: 2, unit: 'px', section: 'icon' },\n { key: 'iconRadius', label: 'Icon Radius', control: 'slider', defaultValue: 6, min: 0, max: 16, step: 1, unit: 'px', section: 'icon' },\n ],\n layoutVariants: [\n {\n key: 'mode',\n label: 'Mode',\n options: [\n // `static` → renders as `<div>`; no hover / focus / active\n // styling. `interactive` → renders as `<button>` (or `<a>`\n // when href is passed); full state styling. Mode is\n // orthogonal to trailing: any trailing can sit in either mode.\n { value: 'static', label: 'Static' },\n { value: 'interactive', label: 'Interactive' },\n ],\n defaultValue: 'interactive',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n // `default` and `disabled` are always available — every row\n // has a resting state, and `disabled` is a visual treatment\n // (dimmed, muted) that applies regardless of mode. The CSS\n // rule includes a bare `.uxm-list-item[aria-disabled]`\n // selector that fires on `<div>` as well as `<button>` /\n // `<a>`. The interactive-only states (hover / focus / active)\n // are option-level-gated to `mode: \"interactive\"`: when the\n // row is static, the picker collapses to \"Default\" +\n // \"Disabled\", which self-explains as \"this row only has\n // visual states\" without the picker disappearing.\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover', showWhen: { mode: 'interactive' } },\n { value: 'focus', label: 'Focus', showWhen: { mode: 'interactive' } },\n { value: 'active', label: 'Active', showWhen: { mode: 'interactive' } },\n { value: 'disabled', label: 'Disabled' },\n ],\n defaultValue: 'default',\n },\n {\n key: 'value',\n label: 'Value Text',\n options: [\n { value: 'shown', label: 'Shown' },\n { value: 'hidden', label: 'Hidden' },\n ],\n defaultValue: 'shown',\n },\n // No `trailing` variant — the preview chooses what to show in\n // the trailing slot based on `mode` (chevron for interactive,\n // Tag for static). Real consumers pass any ReactNode via the\n // atom's `trailing` prop; the editor preview demos the\n // canonical patterns only.\n ],\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const listItemDef: ComponentDef = {\n id: 'list-item',\n name: 'List Item',\n category: 'Composite',\n description: \"Row with a leading slot (accent icon tile, or media rendered as-is for a thumbnail / avatar), title, and optional trailing slot — used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing — trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.\",\n styleProperties: [\n // ── Per-state row colors. Filtered into the \"States\" section by\n // the State variant. Default-state knobs (`inactiveBg`,\n // `inactiveText`) gate on `state: \"default\"` only — so the bg /\n // text are editable in both static and interactive modes. The\n // interactive-only state knobs (hover / focus / active)\n // additionally gate on `mode: \"interactive\"` so they hide\n // entirely when the row is rendered as a display-only `<div>`.\n // `disabled` is a visual treatment, not an interaction state, so\n // its knobs are NOT mode-gated.\n { key: 'inactiveBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'default' } },\n { key: 'inactiveText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'default' } },\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'hoverText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'focusText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'activeBg', label: 'Background', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'activeText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'disabled' } },\n { key: 'disabledText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'states', showWhen: { state: 'disabled' } },\n // Focus ring + disabled opacity in their own sections.\n { key: 'focusRing', label: 'Ring Color', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.5, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // ── Container (List wrapper)\n { key: 'borderColor', label: 'Border Color', control: 'color', defaultValue: 'var(--color-border)', section: 'container' },\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 16, step: 1, unit: 'px', section: 'container' },\n // ── Shared sizing (applies to every state)\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 16, min: 8, max: 32, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'gap', label: 'Icon Gap', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 11, max: 18, step: 1, unit: 'px' },\n // ── Sub-elements (shared across every state)\n { key: 'valueColor', label: 'Value Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'value', showWhen: { value: 'shown' } },\n { key: 'valueSize', label: 'Value Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'value', showWhen: { value: 'shown' } },\n // Trailing slot is purely composable — the consumer passes whatever\n // ReactNode they want. The preview demos the canonical pattern per\n // mode (chevron for interactive nav rows, Tag for static status\n // rows), so a single Chevron Color knob covers the interactive\n // example. Other trailing decorations (Tag, Badge, Avatar, etc.)\n // bring their own styling via their respective atoms.\n { key: 'chevronColor', label: 'Chevron Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'trailing', showWhen: { mode: 'interactive' } },\n // High-contrast icon \"stamp\" — bright accent tile with a card-coloured\n // glyph inside. Keeps the icon visually distinct from the row bg in\n // every state (default / hover / active / disabled), so the shared\n // icon knobs don't need to vary per-state.\n { key: 'iconBg', label: 'Icon Bg', control: 'color', defaultValue: 'var(--color-accent)', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconColor', label: 'Icon Color', control: 'color', defaultValue: 'var(--color-card)', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 24, min: 16, max: 40, step: 2, unit: 'px', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconRadius', label: 'Icon Radius', control: 'slider', defaultValue: 6, min: 0, max: 16, step: 1, unit: 'px', section: 'icon', showWhen: { leading: 'icon' } },\n // Media slot is the un-tiled leading alternative: the passed node\n // (Thumbnail / avatar / <img>) brings its own surface and radius, so\n // the row only owns the box it sits in — one size knob, no colours.\n { key: 'mediaSize', label: 'Media Size', control: 'number', defaultValue: 36, min: 24, max: 64, step: 2, unit: 'px', section: 'media', showWhen: { leading: 'media' } },\n ],\n layoutVariants: [\n {\n key: 'mode',\n label: 'Mode',\n options: [\n // `static` → renders as `<div>`; no hover / focus / active\n // styling. `interactive` → renders as `<button>` (or `<a>`\n // when href is passed); full state styling. Mode is\n // orthogonal to trailing: any trailing can sit in either mode.\n { value: 'static', label: 'Static' },\n { value: 'interactive', label: 'Interactive' },\n ],\n defaultValue: 'interactive',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n // `default` and `disabled` are always available — every row\n // has a resting state, and `disabled` is a visual treatment\n // (dimmed, muted) that applies regardless of mode. The CSS\n // rule includes a bare `.uxm-list-item[aria-disabled]`\n // selector that fires on `<div>` as well as `<button>` /\n // `<a>`. The interactive-only states (hover / focus / active)\n // are option-level-gated to `mode: \"interactive\"`: when the\n // row is static, the picker collapses to \"Default\" +\n // \"Disabled\", which self-explains as \"this row only has\n // visual states\" without the picker disappearing.\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover', showWhen: { mode: 'interactive' } },\n { value: 'focus', label: 'Focus', showWhen: { mode: 'interactive' } },\n { value: 'active', label: 'Active', showWhen: { mode: 'interactive' } },\n { value: 'disabled', label: 'Disabled' },\n ],\n defaultValue: 'default',\n },\n {\n key: 'leading',\n label: 'Leading',\n options: [\n // `icon` → the accent `IconTile` (the long-standing default, so an\n // existing saved theme opens on the same row it was saved against).\n // `media` → the node renders as-is; the preview passes a `Thumbnail`,\n // which is the shape this slot exists for (product / people rows).\n { value: 'icon', label: 'Icon' },\n { value: 'media', label: 'Media' },\n ],\n defaultValue: 'icon',\n },\n {\n key: 'value',\n label: 'Value Text',\n options: [\n { value: 'shown', label: 'Shown' },\n { value: 'hidden', label: 'Hidden' },\n ],\n defaultValue: 'shown',\n },\n // No `trailing` variant — the preview chooses what to show in\n // the trailing slot based on `mode` (chevron for interactive,\n // Tag for static). Real consumers pass any ReactNode via the\n // atom's `trailing` prop; the editor preview demos the\n // canonical patterns only.\n ],\n};\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"list-item.d.ts","sourceRoot":"","sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,WAAW,EAAE,YAyGzB,CAAC"}
1
+ {"version":3,"file":"list-item.d.ts","sourceRoot":"","sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,WAAW,EAAE,YA0HzB,CAAC"}
@@ -2,7 +2,7 @@ const listItemDef = {
2
2
  id: "list-item",
3
3
  name: "List Item",
4
4
  category: "Composite",
5
- description: "Row with icon, title, and optional trailing slot \u2014 used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing \u2014 trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.",
5
+ description: "Row with a leading slot (accent icon tile, or media rendered as-is for a thumbnail / avatar), title, and optional trailing slot \u2014 used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing \u2014 trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.",
6
6
  styleProperties: [
7
7
  // ── Per-state row colors. Filtered into the "States" section by
8
8
  // the State variant. Default-state knobs (`inactiveBg`,
@@ -47,10 +47,14 @@ const listItemDef = {
47
47
  // glyph inside. Keeps the icon visually distinct from the row bg in
48
48
  // every state (default / hover / active / disabled), so the shared
49
49
  // icon knobs don't need to vary per-state.
50
- { key: "iconBg", label: "Icon Bg", control: "color", defaultValue: "var(--color-accent)", section: "icon" },
51
- { key: "iconColor", label: "Icon Color", control: "color", defaultValue: "var(--color-card)", section: "icon" },
52
- { key: "iconSize", label: "Icon Size", control: "number", defaultValue: 24, min: 16, max: 40, step: 2, unit: "px", section: "icon" },
53
- { key: "iconRadius", label: "Icon Radius", control: "slider", defaultValue: 6, min: 0, max: 16, step: 1, unit: "px", section: "icon" }
50
+ { key: "iconBg", label: "Icon Bg", control: "color", defaultValue: "var(--color-accent)", section: "icon", showWhen: { leading: "icon" } },
51
+ { key: "iconColor", label: "Icon Color", control: "color", defaultValue: "var(--color-card)", section: "icon", showWhen: { leading: "icon" } },
52
+ { key: "iconSize", label: "Icon Size", control: "number", defaultValue: 24, min: 16, max: 40, step: 2, unit: "px", section: "icon", showWhen: { leading: "icon" } },
53
+ { key: "iconRadius", label: "Icon Radius", control: "slider", defaultValue: 6, min: 0, max: 16, step: 1, unit: "px", section: "icon", showWhen: { leading: "icon" } },
54
+ // Media slot is the un-tiled leading alternative: the passed node
55
+ // (Thumbnail / avatar / <img>) brings its own surface and radius, so
56
+ // the row only owns the box it sits in — one size knob, no colours.
57
+ { key: "mediaSize", label: "Media Size", control: "number", defaultValue: 36, min: 24, max: 64, step: 2, unit: "px", section: "media", showWhen: { leading: "media" } }
54
58
  ],
55
59
  layoutVariants: [
56
60
  {
@@ -88,6 +92,19 @@ const listItemDef = {
88
92
  ],
89
93
  defaultValue: "default"
90
94
  },
95
+ {
96
+ key: "leading",
97
+ label: "Leading",
98
+ options: [
99
+ // `icon` → the accent `IconTile` (the long-standing default, so an
100
+ // existing saved theme opens on the same row it was saved against).
101
+ // `media` → the node renders as-is; the preview passes a `Thumbnail`,
102
+ // which is the shape this slot exists for (product / people rows).
103
+ { value: "icon", label: "Icon" },
104
+ { value: "media", label: "Media" }
105
+ ],
106
+ defaultValue: "icon"
107
+ },
91
108
  {
92
109
  key: "value",
93
110
  label: "Value Text",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const listItemDef: ComponentDef = {\n id: 'list-item',\n name: 'List Item',\n category: 'Composite',\n description: \"Row with icon, title, and optional trailing slot — used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing — trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.\",\n styleProperties: [\n // ── Per-state row colors. Filtered into the \"States\" section by\n // the State variant. Default-state knobs (`inactiveBg`,\n // `inactiveText`) gate on `state: \"default\"` only — so the bg /\n // text are editable in both static and interactive modes. The\n // interactive-only state knobs (hover / focus / active)\n // additionally gate on `mode: \"interactive\"` so they hide\n // entirely when the row is rendered as a display-only `<div>`.\n // `disabled` is a visual treatment, not an interaction state, so\n // its knobs are NOT mode-gated.\n { key: 'inactiveBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'default' } },\n { key: 'inactiveText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'default' } },\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'hoverText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'focusText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'activeBg', label: 'Background', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'activeText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'disabled' } },\n { key: 'disabledText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'states', showWhen: { state: 'disabled' } },\n // Focus ring + disabled opacity in their own sections.\n { key: 'focusRing', label: 'Ring Color', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.5, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // ── Container (List wrapper)\n { key: 'borderColor', label: 'Border Color', control: 'color', defaultValue: 'var(--color-border)', section: 'container' },\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 16, step: 1, unit: 'px', section: 'container' },\n // ── Shared sizing (applies to every state)\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 16, min: 8, max: 32, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'gap', label: 'Icon Gap', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 11, max: 18, step: 1, unit: 'px' },\n // ── Sub-elements (shared across every state)\n { key: 'valueColor', label: 'Value Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'value', showWhen: { value: 'shown' } },\n { key: 'valueSize', label: 'Value Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'value', showWhen: { value: 'shown' } },\n // Trailing slot is purely composable — the consumer passes whatever\n // ReactNode they want. The preview demos the canonical pattern per\n // mode (chevron for interactive nav rows, Tag for static status\n // rows), so a single Chevron Color knob covers the interactive\n // example. Other trailing decorations (Tag, Badge, Avatar, etc.)\n // bring their own styling via their respective atoms.\n { key: 'chevronColor', label: 'Chevron Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'trailing', showWhen: { mode: 'interactive' } },\n // High-contrast icon \"stamp\" — bright accent tile with a card-coloured\n // glyph inside. Keeps the icon visually distinct from the row bg in\n // every state (default / hover / active / disabled), so the shared\n // icon knobs don't need to vary per-state.\n { key: 'iconBg', label: 'Icon Bg', control: 'color', defaultValue: 'var(--color-accent)', section: 'icon' },\n { key: 'iconColor', label: 'Icon Color', control: 'color', defaultValue: 'var(--color-card)', section: 'icon' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 24, min: 16, max: 40, step: 2, unit: 'px', section: 'icon' },\n { key: 'iconRadius', label: 'Icon Radius', control: 'slider', defaultValue: 6, min: 0, max: 16, step: 1, unit: 'px', section: 'icon' },\n ],\n layoutVariants: [\n {\n key: 'mode',\n label: 'Mode',\n options: [\n // `static` → renders as `<div>`; no hover / focus / active\n // styling. `interactive` → renders as `<button>` (or `<a>`\n // when href is passed); full state styling. Mode is\n // orthogonal to trailing: any trailing can sit in either mode.\n { value: 'static', label: 'Static' },\n { value: 'interactive', label: 'Interactive' },\n ],\n defaultValue: 'interactive',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n // `default` and `disabled` are always available — every row\n // has a resting state, and `disabled` is a visual treatment\n // (dimmed, muted) that applies regardless of mode. The CSS\n // rule includes a bare `.uxm-list-item[aria-disabled]`\n // selector that fires on `<div>` as well as `<button>` /\n // `<a>`. The interactive-only states (hover / focus / active)\n // are option-level-gated to `mode: \"interactive\"`: when the\n // row is static, the picker collapses to \"Default\" +\n // \"Disabled\", which self-explains as \"this row only has\n // visual states\" without the picker disappearing.\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover', showWhen: { mode: 'interactive' } },\n { value: 'focus', label: 'Focus', showWhen: { mode: 'interactive' } },\n { value: 'active', label: 'Active', showWhen: { mode: 'interactive' } },\n { value: 'disabled', label: 'Disabled' },\n ],\n defaultValue: 'default',\n },\n {\n key: 'value',\n label: 'Value Text',\n options: [\n { value: 'shown', label: 'Shown' },\n { value: 'hidden', label: 'Hidden' },\n ],\n defaultValue: 'shown',\n },\n // No `trailing` variant — the preview chooses what to show in\n // the trailing slot based on `mode` (chevron for interactive,\n // Tag for static). Real consumers pass any ReactNode via the\n // atom's `trailing` prop; the editor preview demos the\n // canonical patterns only.\n ],\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../src/studio/lib/registry/composite/list-item.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const listItemDef: ComponentDef = {\n id: 'list-item',\n name: 'List Item',\n category: 'Composite',\n description: \"Row with a leading slot (accent icon tile, or media rendered as-is for a thumbnail / avatar), title, and optional trailing slot — used in stacked lists. Renders as `<div>` (static) or `<button>` / `<a>` (interactive) depending on the atom's `interactive` / `href` props. State knobs gate on `mode` rather than trailing — trailing is purely a visual choice and the two are orthogonal: an interactive row can have any trailing, a static row can have a chevron decoration without being tappable.\",\n styleProperties: [\n // ── Per-state row colors. Filtered into the \"States\" section by\n // the State variant. Default-state knobs (`inactiveBg`,\n // `inactiveText`) gate on `state: \"default\"` only — so the bg /\n // text are editable in both static and interactive modes. The\n // interactive-only state knobs (hover / focus / active)\n // additionally gate on `mode: \"interactive\"` so they hide\n // entirely when the row is rendered as a display-only `<div>`.\n // `disabled` is a visual treatment, not an interaction state, so\n // its knobs are NOT mode-gated.\n { key: 'inactiveBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'default' } },\n { key: 'inactiveText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'default' } },\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'hoverText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'hover', mode: 'interactive' } },\n { key: 'focusText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text)', section: 'states', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'activeBg', label: 'Background', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'activeText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'states', showWhen: { state: 'active', mode: 'interactive' } },\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'states', showWhen: { state: 'disabled' } },\n { key: 'disabledText', label: 'Text Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'states', showWhen: { state: 'disabled' } },\n // Focus ring + disabled opacity in their own sections.\n { key: 'focusRing', label: 'Ring Color', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus', mode: 'interactive' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.5, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // ── Container (List wrapper)\n { key: 'borderColor', label: 'Border Color', control: 'color', defaultValue: 'var(--color-border)', section: 'container' },\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 16, step: 1, unit: 'px', section: 'container' },\n // ── Shared sizing (applies to every state)\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 16, min: 8, max: 32, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'gap', label: 'Icon Gap', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 11, max: 18, step: 1, unit: 'px' },\n // ── Sub-elements (shared across every state)\n { key: 'valueColor', label: 'Value Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'value', showWhen: { value: 'shown' } },\n { key: 'valueSize', label: 'Value Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'value', showWhen: { value: 'shown' } },\n // Trailing slot is purely composable — the consumer passes whatever\n // ReactNode they want. The preview demos the canonical pattern per\n // mode (chevron for interactive nav rows, Tag for static status\n // rows), so a single Chevron Color knob covers the interactive\n // example. Other trailing decorations (Tag, Badge, Avatar, etc.)\n // bring their own styling via their respective atoms.\n { key: 'chevronColor', label: 'Chevron Color', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'trailing', showWhen: { mode: 'interactive' } },\n // High-contrast icon \"stamp\" — bright accent tile with a card-coloured\n // glyph inside. Keeps the icon visually distinct from the row bg in\n // every state (default / hover / active / disabled), so the shared\n // icon knobs don't need to vary per-state.\n { key: 'iconBg', label: 'Icon Bg', control: 'color', defaultValue: 'var(--color-accent)', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconColor', label: 'Icon Color', control: 'color', defaultValue: 'var(--color-card)', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 24, min: 16, max: 40, step: 2, unit: 'px', section: 'icon', showWhen: { leading: 'icon' } },\n { key: 'iconRadius', label: 'Icon Radius', control: 'slider', defaultValue: 6, min: 0, max: 16, step: 1, unit: 'px', section: 'icon', showWhen: { leading: 'icon' } },\n // Media slot is the un-tiled leading alternative: the passed node\n // (Thumbnail / avatar / <img>) brings its own surface and radius, so\n // the row only owns the box it sits in — one size knob, no colours.\n { key: 'mediaSize', label: 'Media Size', control: 'number', defaultValue: 36, min: 24, max: 64, step: 2, unit: 'px', section: 'media', showWhen: { leading: 'media' } },\n ],\n layoutVariants: [\n {\n key: 'mode',\n label: 'Mode',\n options: [\n // `static` → renders as `<div>`; no hover / focus / active\n // styling. `interactive` → renders as `<button>` (or `<a>`\n // when href is passed); full state styling. Mode is\n // orthogonal to trailing: any trailing can sit in either mode.\n { value: 'static', label: 'Static' },\n { value: 'interactive', label: 'Interactive' },\n ],\n defaultValue: 'interactive',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n // `default` and `disabled` are always available — every row\n // has a resting state, and `disabled` is a visual treatment\n // (dimmed, muted) that applies regardless of mode. The CSS\n // rule includes a bare `.uxm-list-item[aria-disabled]`\n // selector that fires on `<div>` as well as `<button>` /\n // `<a>`. The interactive-only states (hover / focus / active)\n // are option-level-gated to `mode: \"interactive\"`: when the\n // row is static, the picker collapses to \"Default\" +\n // \"Disabled\", which self-explains as \"this row only has\n // visual states\" without the picker disappearing.\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover', showWhen: { mode: 'interactive' } },\n { value: 'focus', label: 'Focus', showWhen: { mode: 'interactive' } },\n { value: 'active', label: 'Active', showWhen: { mode: 'interactive' } },\n { value: 'disabled', label: 'Disabled' },\n ],\n defaultValue: 'default',\n },\n {\n key: 'leading',\n label: 'Leading',\n options: [\n // `icon` → the accent `IconTile` (the long-standing default, so an\n // existing saved theme opens on the same row it was saved against).\n // `media` → the node renders as-is; the preview passes a `Thumbnail`,\n // which is the shape this slot exists for (product / people rows).\n { value: 'icon', label: 'Icon' },\n { value: 'media', label: 'Media' },\n ],\n defaultValue: 'icon',\n },\n {\n key: 'value',\n label: 'Value Text',\n options: [\n { value: 'shown', label: 'Shown' },\n { value: 'hidden', label: 'Hidden' },\n ],\n defaultValue: 'shown',\n },\n // No `trailing` variant — the preview chooses what to show in\n // the trailing slot based on `mode` (chevron for interactive,\n // Tag for static). Real consumers pass any ReactNode via the\n // atom's `trailing` prop; the editor preview demos the\n // canonical patterns only.\n ],\n};\n"],"mappings":"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","names":[]}
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── app-sidebar ── */
3
2
  /* AppSidebar — chrome shell. Root holds the visible width/bg/border-right;
4
3
  inner __heading and __footer read their own colour vars so the editor
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── app-top-bar ── */
3
2
  /* AppTopBar — root flex container holds height/padding-x/gap directly
4
3
  (REAL_CSS_PROPS land cleanly). Inner __search reads its max-width var
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── avatar ── */
3
2
  .uxm-avatar {
4
3
  align-items: center;
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── back-link ── */
3
2
  .uxm-back-link {
4
3
  align-items: center;
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── badge ── */
3
2
  /* Badge — notification indicator. Two modes (dot, count) × six tones.
4
3
  - Count mode: root IS the pill — bg + text + padding + radius land on it.
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── banner ── */
3
2
  .uxm-banner {
4
3
  display: flex;
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── breadcrumb ── */
3
2
  /* Breadcrumb — composes Link for clickable crumbs. Uses descendant
4
3
  selectors (.uxm-breadcrumb .uxm-link) so breadcrumb-scope styling wins
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── Bulk Action Bar ──
3
2
  Floating toolbar shown once rows are selected: "{N} selected · actions ·
4
3
  × clear". The `.uxm-bulk-action-bar` root IS the styled element (card
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── button ── */
3
2
  /* Per-component theming contract: every colour/border the UXM studio exposes
4
3
  (default + hover/active/focus/disabled) is read from a `--uxm-button-{variant}-*`
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── button-group ── */
3
2
  /* ButtonGroup — root is the segmented track; per-item bg/text/padding/
4
3
  font-size live on `.uxm-button-group__item`. The inner border-right
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ButtonIcon — moved from monolithic styles.css */
3
2
  .uxm-button-icon {
4
3
  align-items: center;
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── button-with-icon ── */
3
2
  .uxm-button-with-icon {
4
3
  align-items: center;
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* Calendar — moved from monolithic styles.css */
3
2
  /* Soft elevation so the calendar reads as a floating surface in its popover
4
3
  usages (DateInput / TimeInput / EditableCell date). On by default via the
@@ -1,4 +1,3 @@
1
- @charset "UTF-8";
2
1
  /* ── card ── */
3
2
  /* Card — every registry knob is a CSS variable. The shadow toggle from
4
3
  the registry stays as a modifier class; consumers either include the