@attrus-ds/core 0.1.1 → 0.2.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 (55) hide show
  1. package/README.md +63 -9
  2. package/dist/css/components/buttons.css +1 -1
  3. package/dist/css/components/combobox.css +17 -1
  4. package/dist/css/components/inputs.css +3 -55
  5. package/dist/css/components/overlays.css +28 -59
  6. package/dist/css/components/primitives.css +10 -1
  7. package/dist/css/components/sidebar.css +3 -3
  8. package/dist/css/components/smart-search.css +91 -0
  9. package/dist/css/components/table.css +10 -8
  10. package/dist/css/components/tabs.css +2 -2
  11. package/dist/css/components/toast.css +4 -0
  12. package/dist/css/components/tooltip.css +5 -0
  13. package/dist/css/index.css +1 -0
  14. package/dist/react/AppSidebar/AppSidebar.d.ts +50 -0
  15. package/dist/react/AppSidebar/AppSidebar.tsx +197 -0
  16. package/dist/react/AppTopbar/AppTopbar.d.ts +58 -0
  17. package/dist/react/AppTopbar/AppTopbar.tsx +203 -0
  18. package/dist/react/Atoms/Atoms.d.ts +149 -0
  19. package/dist/react/Atoms/Atoms.tsx +407 -0
  20. package/dist/react/Button/Button.d.ts +25 -0
  21. package/dist/react/Button/Button.tsx +83 -0
  22. package/dist/react/Callout/Callout.d.ts +29 -0
  23. package/dist/react/Callout/Callout.tsx +98 -0
  24. package/dist/react/Card/Card.d.ts +38 -0
  25. package/dist/react/Card/Card.tsx +93 -0
  26. package/dist/react/Combobox/Combobox.d.ts +52 -0
  27. package/dist/react/Combobox/Combobox.tsx +254 -0
  28. package/dist/react/DataTable/DataTable.d.ts +81 -0
  29. package/dist/react/DataTable/DataTable.tsx +308 -0
  30. package/dist/react/Drawer/Drawer.d.ts +35 -0
  31. package/dist/react/Drawer/Drawer.tsx +226 -0
  32. package/dist/react/Input/Input.d.ts +155 -0
  33. package/dist/react/Input/Input.tsx +478 -0
  34. package/dist/react/Modal/Modal.d.ts +27 -0
  35. package/dist/react/Modal/Modal.tsx +173 -0
  36. package/dist/react/NumberInput/NumberInput.d.ts +25 -0
  37. package/dist/react/NumberInput/NumberInput.tsx +117 -0
  38. package/dist/react/Pill/Pill.d.ts +31 -0
  39. package/dist/react/Pill/Pill.tsx +104 -0
  40. package/dist/react/SearchPill/SearchPill.d.ts +14 -0
  41. package/dist/react/SearchPill/SearchPill.tsx +40 -0
  42. package/dist/react/SmartSearch/SmartSearch.d.ts +65 -0
  43. package/dist/react/SmartSearch/SmartSearch.tsx +271 -0
  44. package/dist/react/Stepper/Stepper.d.ts +25 -0
  45. package/dist/react/Stepper/Stepper.tsx +94 -0
  46. package/dist/react/Tabs/Tabs.d.ts +32 -0
  47. package/dist/react/Tabs/Tabs.tsx +147 -0
  48. package/dist/react/Toast/Toast.d.ts +30 -0
  49. package/dist/react/Toast/Toast.tsx +94 -0
  50. package/dist/react/Tooltip/Tooltip.d.ts +23 -0
  51. package/dist/react/Tooltip/Tooltip.tsx +80 -0
  52. package/dist/react/ZeroState/ZeroState.d.ts +17 -0
  53. package/dist/react/ZeroState/ZeroState.tsx +45 -0
  54. package/dist/react/index.ts +35 -0
  55. package/package.json +12 -2
package/README.md CHANGED
@@ -1,15 +1,18 @@
1
1
  # @attrus-ds/core
2
2
 
3
- Visual layer of the **ATTRUS / Cabiros** design system, packaged for npm.
3
+ The **ATTRUS / Cabiros** design system, packaged for npm.
4
4
 
5
- This package ships **only the visual foundation** — design tokens, component
6
- styles, brand assets and self-hosted fonts. It contains **no framework
7
- components** (no React/Vue/etc.) and no application screens.
5
+ The CSS is the single visual source of truth — design tokens, component styles,
6
+ brand assets and self-hosted fonts. On top of that, an **optional typed React
7
+ layer** ships as source (TSX + `.d.ts`) under `@attrus-ds/core/react`; it only
8
+ composes the canonical class names, so the CSS-only / markup path stays fully
9
+ valid. No application screens are included.
8
10
 
9
11
  What's inside:
10
12
 
11
13
  - **Design tokens** — colors, typography, spacing, radii, shadows, motion, data-viz (CSS custom properties + JSON).
12
- - **Component styles** — ready-to-use CSS for buttons, inputs, tables, pills, tabs, cards, callouts, overlays and more.
14
+ - **Component styles** — ready-to-use CSS for buttons, inputs, tables, pills, tabs, cards, callouts, overlays, smart-search and more.
15
+ - **React components** — 20 typed wrappers (Button, Pill, Card, DataTable, Combobox, SmartSearch, Modal, Drawer, AppSidebar…), shipped as source under `@attrus-ds/core/react`. `react` is an optional peer dependency.
13
16
  - **Brand assets** — logos, icons and country flags (SVG).
14
17
  - **Fonts** — Lato and JetBrains Mono, self-hosted (`.woff2`, no CDN).
15
18
 
@@ -74,8 +77,38 @@ import "@attrus-ds/core/components/table.css";
74
77
  Available component stylesheets:
75
78
 
76
79
  `buttons` · `callout` · `cards` · `combobox` · `empty-states` · `inputs` ·
77
- `number-input` · `overlays` · `primitives` · `sidebar` · `status-pills` ·
78
- `stepper` · `table` · `tabs` · `toast` · `tooltip` · `topbar`
80
+ `number-input` · `overlays` · `primitives` · `sidebar` · `smart-search` ·
81
+ `status-pills` · `stepper` · `table` · `tabs` · `toast` · `tooltip` · `topbar`
82
+
83
+ ### React components
84
+
85
+ The typed React layer is shipped as TSX source under `@attrus-ds/core/react`.
86
+ Your bundler/toolchain compiles it; `react` (>= 18) is an optional peer
87
+ dependency. Import the styles once at your app root, then the components:
88
+
89
+ ```js
90
+ import "@attrus-ds/core/css"; // styles (once, at the root)
91
+ import { Button, Pill, DataTable } from "@attrus-ds/core/react";
92
+
93
+ export function Example() {
94
+ return <Button variant="primary" size="sm">New transaction</Button>;
95
+ }
96
+ ```
97
+
98
+ Each component is also importable on its own path:
99
+
100
+ ```js
101
+ import { Button } from "@attrus-ds/core/react/Button/Button";
102
+ ```
103
+
104
+ The CSS stays the single visual source of truth — these wrappers add a typed API
105
+ and compose the same canonical classes (`.btn btn-primary`, `.pill success`, …),
106
+ so plain HTML consumers and React consumers render identically.
107
+
108
+ Component roster: `Button` · `Pill` · `Callout` · `Tabs` · `Input` ·
109
+ `SearchPill` · `Card` · `DataTable` · `Atoms` (Money/Metric/Avatar/Eyebrow/Flag/
110
+ Divider…) · `Stepper` · `NumberInput` · `Combobox` · `SmartSearch` · `Toast` ·
111
+ `Tooltip` · `ZeroState` · `Modal` · `Drawer` · `AppSidebar` · `AppTopbar`.
79
112
 
80
113
  ### Fonts
81
114
 
@@ -122,12 +155,17 @@ src/
122
155
  │ ├── typography.css # type scale / weights / line-height / tracking tokens
123
156
  │ └── theme-dark.css # [data-theme="dark"] semantic overrides
124
157
  ├── typography/index.css # ready-to-use type classes + text utilities
125
- └── components/ # one stylesheet per component domain (+ index.css barrel)
158
+ ├── components/ # one stylesheet per component domain (+ index.css barrel)
159
+ └── react/ # typed React layer — one folder per component
160
+ ├── index.ts # barrel re-exporting every component
161
+ └── <Name>/<Name>.tsx + <Name>.d.ts
126
162
  ```
127
163
 
128
164
  The published artifact is assembled into `dist/` by a dependency-free Node
129
165
  script (`scripts/build.mjs`). The token partials are concatenated into a single
130
- `dist/css/tokens.css`, so consumer import paths are unchanged.
166
+ `dist/css/tokens.css`, the component CSS is copied per-file, and the React
167
+ sources are copied verbatim to `dist/react/` (no compilation — consumers build
168
+ them), so consumer import paths are unchanged.
131
169
 
132
170
  ```bash
133
171
  npm install
@@ -135,6 +173,22 @@ npm run build # writes dist/
135
173
  npm pack # inspect the tarball before publishing
136
174
  ```
137
175
 
176
+ ### Components showcase
177
+
178
+ `preview/components.html` is the design-system home — a navigable index of foundations, components, brand assets and package entry points (same structure as the Cabiros `design-system.html`).
179
+
180
+ Each component has a dedicated spec page under `preview/` (e.g. `buttons.html`, `inputs.html`) with anatomy, variants, states, composition, don'ts, accessibility and token tables.
181
+
182
+ `preview/gallery.html` is a compact live-specimen reference — useful for quick visual checks, but not the full documentation.
183
+
184
+ Build first, then open the home page in a browser:
185
+
186
+ ```bash
187
+ npm run build
188
+ node scripts/port-preview.mjs # re-sync spec pages from cabiros when upstream changes
189
+ # then open preview/components.html
190
+ ```
191
+
138
192
  ## License
139
193
 
140
194
  See [LICENSE](LICENSE). Bundled fonts (Lato, JetBrains Mono) are distributed
@@ -206,7 +206,7 @@
206
206
  border-color: var(--color-feedback-danger-border);
207
207
  }
208
208
  .btn-danger-outline:hover { background: var(--color-feedback-danger-background); border-color: var(--color-danger-500); }
209
- .btn-danger-outline:active { background: color-mix(in srgb, var(--color-feedback-danger-background) 60%, var(--color-danger-100)); }
209
+ .btn-danger-outline:active { background: color-mix(in srgb, var(--color-feedback-danger-background) 85%, var(--color-danger-500)); }
210
210
  .btn-danger-outline:focus-visible { box-shadow: var(--shadow-focus-danger); }
211
211
 
212
212
 
@@ -171,7 +171,7 @@
171
171
  background: var(--color-background-hover);
172
172
  }
173
173
  .combobox-option.is-selected {
174
- background: var(--color-brand-secondary-50);
174
+ background: var(--color-background-selected);
175
175
  color: var(--color-foreground-accent);
176
176
  font-weight: var(--w-semibold);
177
177
  }
@@ -296,6 +296,22 @@
296
296
  box-shadow: 0 0 0 3px var(--color-feedback-danger-background);
297
297
  }
298
298
 
299
+
300
+ /* ----------------------------------------------------------------
301
+ LISTING POPOVER EXTRAS — optional rows inside the popover:
302
+ filter-chip row (below search), results meta (above list),
303
+ foot meta (below list). Exposed via Combobox props
304
+ `filters` / `meta` / `footMeta`.
305
+ ---------------------------------------------------------------- */
306
+ .combobox-filters { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-3); border-bottom: 1px solid var(--color-border-subtle); }
307
+ .combobox-filter-chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: 4px var(--space-3); background: var(--color-background-surface); border: 1px solid var(--color-border-default); border-radius: var(--radius-pill); font-family: var(--font-sans); font-size: var(--fs-xs); font-weight: var(--w-medium); color: var(--color-foreground-secondary); cursor: pointer; transition: var(--transition-fast); }
308
+ .combobox-filter-chip:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); color: var(--color-foreground-default); }
309
+ .combobox-filter-chip.is-active { background: var(--color-background-selected); border-color: var(--color-brand-secondary-200); color: var(--color-foreground-brand); font-weight: var(--w-semibold); }
310
+ .combobox-filter-chip .count { font-family: var(--font-mono); font-size: var(--fs-xxs); color: var(--color-foreground-muted); }
311
+ .combobox-filter-chip.is-active .count { color: var(--color-brand-secondary-600); }
312
+ .combobox-meta { padding: var(--space-2) var(--space-3) var(--space-1); font-size: var(--fs-xxs); font-weight: var(--w-semibold); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--color-foreground-muted); border-bottom: 1px solid var(--color-border-subtle); }
313
+ .combobox-foot-meta { padding: var(--space-2) var(--space-3); font-size: var(--fs-xs); color: var(--color-foreground-muted); border-top: 1px solid var(--color-border-subtle); }
314
+
299
315
  /* Reduced motion */
300
316
  @media (prefers-reduced-motion: reduce) {
301
317
  .combobox-chevron { transition: none; }
@@ -455,59 +455,7 @@ textarea.input.input-lg { height: auto; min-height: 120px; padding: var(--space-
455
455
  }
456
456
 
457
457
 
458
- /* ----------------------------------------------------------------
459
- 10. NUMBER INPUT (with +/- buttons)
460
- ---------------------------------------------------------------- */
461
- .number-input {
462
- display: inline-flex;
463
- align-items: stretch;
464
- height: 40px;
465
- border: 1px solid var(--color-border-default);
466
- border-radius: var(--radius-sm);
467
- background: var(--color-background-surface);
468
- overflow: hidden;
469
- transition:
470
- border-color var(--transition-fast),
471
- box-shadow var(--transition-fast);
472
- }
473
- .number-input:focus-within {
474
- border-color: var(--color-border-focus);
475
- box-shadow: var(--shadow-focus);
476
- }
477
- .number-input button {
478
- width: 36px;
479
- border: 0;
480
- background: var(--color-background-subtle);
481
- color: var(--color-foreground-default);
482
- cursor: pointer;
483
- display: inline-flex;
484
- align-items: center;
485
- justify-content: center;
486
- font-family: var(--font-sans);
487
- font-size: var(--fs-md);
488
- font-weight: var(--w-semibold);
489
- transition: background var(--transition-fast);
490
- }
491
- .number-input button:hover { background: var(--color-background-hover); }
492
- .number-input button:active { background: var(--color-background-press); }
493
- .number-input button:disabled { opacity: 0.4; cursor: not-allowed; }
494
- .number-input button.minus { border-right: 1px solid var(--color-border-default); }
495
- .number-input button.plus { border-left: 1px solid var(--color-border-default); }
496
- .number-input > input {
497
- width: 64px;
498
- border: 0;
499
- outline: none;
500
- text-align: center;
501
- font-family: var(--font-mono);
502
- font-size: var(--fs-md);
503
- font-weight: var(--w-semibold);
504
- background: transparent;
505
- color: var(--color-foreground-default);
506
- -moz-appearance: textfield;
507
- }
508
- .number-input > input::-webkit-outer-spin-button,
509
- .number-input > input::-webkit-inner-spin-button { appearance: none; margin: 0; }
510
-
458
+ /* (NUMBER INPUT moved to number-input.css — canonical single source) */
511
459
 
512
460
  /* ----------------------------------------------------------------
513
461
  11. OTP — one-time password digit cells
@@ -927,12 +875,12 @@ textarea.input.input-lg { height: auto; min-height: 120px; padding: var(--space-
927
875
  }
928
876
  .slider::-webkit-slider-runnable-track {
929
877
  height: 4px;
930
- background: var(--color-neutral-150);
878
+ background: var(--color-border-default);
931
879
  border-radius: var(--radius-pill);
932
880
  }
933
881
  .slider::-moz-range-track {
934
882
  height: 4px;
935
- background: var(--color-neutral-150);
883
+ background: var(--color-border-default);
936
884
  border-radius: var(--radius-pill);
937
885
  }
938
886
  .slider::-webkit-slider-thumb {
@@ -186,76 +186,27 @@
186
186
  opacity var(--duration-slower) var(--ease-standard);
187
187
  }
188
188
 
189
- /* Right (default).
190
- --drawer-w drives both the width AND stack offsets so size variants
191
- automatically scale the stacking math. --drawer-offset shifts the panel
192
- inward when stacked behind another drawer. */
189
+ /* Right (default). --drawer-w drives the width per size variant. */
193
190
  .drawer-right {
194
- --drawer-w: min(440px, 90vw);
195
- --drawer-offset: 0px;
191
+ --drawer-w: min(440px, 90vw); /* @kind spacing */
196
192
  top: 0; right: 0; bottom: 0;
197
193
  width: var(--drawer-w);
198
194
  border-top-left-radius: var(--radius-xs);
199
195
  border-bottom-left-radius: var(--radius-xs);
200
196
  transform: translateX(100%);
201
197
  }
202
- .drawer-right.is-open { transform: translateX(calc(-1 * var(--drawer-offset))); }
198
+ .drawer-right.is-open { transform: translateX(0); }
203
199
 
204
200
  /* Left */
205
201
  .drawer-left {
206
- --drawer-w: min(320px, 85vw);
207
- --drawer-offset: 0px;
202
+ --drawer-w: min(320px, 85vw); /* @kind spacing */
208
203
  top: 0; left: 0; bottom: 0;
209
204
  width: var(--drawer-w);
210
205
  border-top-right-radius: var(--radius-xs);
211
206
  border-bottom-right-radius: var(--radius-xs);
212
207
  transform: translateX(-100%);
213
208
  }
214
- .drawer-left.is-open { transform: translateX(var(--drawer-offset)); }
215
-
216
- /* =================== STACKED DRAWERS ===================
217
- Open a second drawer alongside the first instead of replacing it.
218
- --drawer-stack = how many slots inward to shift; 0 = frontmost panel.
219
- Slot width derived from --drawer-w so size variants stack correctly.
220
- Cap at 3 stacked panels for screens ≥1280px; 2 below.
221
- Usage: <aside class="drawer drawer-right drawer-md" data-stack="1"> */
222
- .drawer-right[data-stack="1"], .drawer-left[data-stack="1"] { --drawer-offset: var(--drawer-w); }
223
- .drawer-right[data-stack="2"], .drawer-left[data-stack="2"] { --drawer-offset: calc(var(--drawer-w) * 2); }
224
- .drawer-right[data-stack="3"], .drawer-left[data-stack="3"] { --drawer-offset: calc(var(--drawer-w) * 3); }
225
-
226
- /* Stacked panels lose their inner radius — they butt up against a sibling.
227
- Separator is placed on the edge that TOUCHES the adjacent panel.
228
- `:has(~)` only matches FOLLOWING siblings, so we apply rules from the
229
- panel that comes EARLIER in the DOM (the back panel, which is rendered
230
- before the front panel in modal-drawer.html). */
231
-
232
- /* Back panel (stack ≥ 1): when followed by an open front panel,
233
- only flatten the INNER edge (right side) and add separator there.
234
- Outer edge (left side) keeps the rounded corners. */
235
- .drawer-right[data-stack]:not([data-stack="0"]).is-open:has(~ .drawer-right[data-stack="0"].is-open) {
236
- border-top-right-radius: 0;
237
- border-bottom-right-radius: 0;
238
- border-right: 2px solid var(--color-border-default);
239
- }
240
- .drawer-left[data-stack]:not([data-stack="0"]).is-open:has(~ .drawer-left[data-stack="0"].is-open) {
241
- border-top-left-radius: 0;
242
- border-bottom-left-radius: 0;
243
- border-left: 2px solid var(--color-border-default);
244
- }
245
-
246
- /* Front panel (stack=0): when PRECEDED by an open back panel, flatten
247
- the inner-side radius and add the matching separator. Use the parent's
248
- `:has` to detect the configuration, then target the front. */
249
- :has(> .drawer-right[data-stack]:not([data-stack="0"]).is-open) > .drawer-right[data-stack="0"].is-open {
250
- border-top-left-radius: 0;
251
- border-bottom-left-radius: 0;
252
- border-left: 2px solid var(--color-border-default);
253
- }
254
- :has(> .drawer-left[data-stack]:not([data-stack="0"]).is-open) > .drawer-left[data-stack="0"].is-open {
255
- border-top-right-radius: 0;
256
- border-bottom-right-radius: 0;
257
- border-right: 2px solid var(--color-border-default);
258
- }
209
+ .drawer-left.is-open { transform: translateX(0); }
259
210
 
260
211
  /* Bottom */
261
212
  .drawer-bottom {
@@ -267,11 +218,11 @@
267
218
  }
268
219
  .drawer-bottom.is-open { transform: translateY(0); }
269
220
 
270
- /* Drawer sizes (right/left) — feed --drawer-w so stacking math scales */
271
- .drawer-sm { --drawer-w: min(360px, 90vw); width: var(--drawer-w); }
272
- .drawer-md { --drawer-w: min(440px, 90vw); width: var(--drawer-w); }
273
- .drawer-lg { --drawer-w: min(560px, 92vw); width: var(--drawer-w); }
274
- .drawer-xl { --drawer-w: min(720px, 94vw); width: var(--drawer-w); }
221
+ /* Drawer sizes (right/left) — feed --drawer-w so widths scale per variant */
222
+ .drawer-sm { --drawer-w: min(360px, 90vw); /* @kind spacing */ width: var(--drawer-w); }
223
+ .drawer-md { --drawer-w: min(440px, 90vw); /* @kind spacing */ width: var(--drawer-w); }
224
+ .drawer-lg { --drawer-w: min(560px, 92vw); /* @kind spacing */ width: var(--drawer-w); }
225
+ .drawer-xl { --drawer-w: min(720px, 94vw); /* @kind spacing */ width: var(--drawer-w); }
275
226
 
276
227
  /* Drawer parts (mirror modal vocabulary).
277
228
  Header and footer are STICKY with a liquid-glass background: body
@@ -326,6 +277,24 @@
326
277
  transition: var(--transition-fast);
327
278
  }
328
279
  .drawer-close:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
280
+
281
+ /* Footer aux — left-side total/note when the footer is space-between (footerAux prop) */
282
+ .drawer-footer-aux { font-size: var(--fs-xs); color: var(--color-foreground-muted); min-width: 0; }
283
+
284
+ /* Mobile: footer actions stack full-width (doc §Header & Footer, variant 7) */
285
+ @media (max-width: 560px) {
286
+ .drawer-footer { flex-direction: column; align-items: stretch; gap: var(--space-2); padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); }
287
+ .drawer-footer .btn { width: 100%; justify-content: center; }
288
+ .drawer-footer-aux { text-align: center; margin-bottom: var(--space-1); }
289
+ }
290
+
291
+ /* Dense header — tight chrome for utility drawers (filters) */
292
+ .drawer-header.is-dense { padding: var(--space-3) var(--space-4); }
293
+ .drawer-header.is-dense .drawer-title { font-size: var(--fs-md); }
294
+
295
+ /* Header with a second control row (search pill, tabs) */
296
+ .drawer-header.has-extra { flex-wrap: wrap; }
297
+ .drawer-header > .drawer-header-extra { flex-basis: 100%; min-width: 0; }
329
298
  .drawer-close:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
330
299
  .drawer-close i, .drawer-close svg { width: 18px; height: 18px; stroke-width: var(--icon-stroke); }
331
300
 
@@ -177,6 +177,8 @@
177
177
  color: var(--color-foreground-secondary);
178
178
  margin-top: var(--space-1);
179
179
  }
180
+ /* Bare stat — figure block without tile chrome, for use inside a Card */
181
+ .stat.is-bare { background: none; border: 0; padding: 0; border-radius: 0; }
180
182
 
181
183
  /* Compact variant — for sidebars / dense headers */
182
184
  .stat-compact {
@@ -275,13 +277,20 @@
275
277
  }
276
278
  .filter-chip:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); }
277
279
  .filter-chip:active { background: var(--color-background-press); }
280
+ /* Selected (aria-pressed via FilterChip TSX) — same active treatment as combobox filter chips */
281
+ .filter-chip[aria-pressed="true"] {
282
+ background: var(--color-background-selected);
283
+ border-color: var(--color-brand-secondary-200);
284
+ color: var(--color-foreground-brand);
285
+ font-weight: var(--w-semibold);
286
+ }
278
287
  .filter-chip .chip-chevron { opacity: .55; }
279
288
 
280
289
  /* =================== PROGRESS BAR =================== */
281
290
  /* Slim track + fill. Width of fill is data-driven (set inline). */
282
291
  .progress {
283
292
  height: var(--progress-h, 6px);
284
- background: var(--progress-track, var(--color-neutral-100));
293
+ background: var(--progress-track, var(--color-border-default));
285
294
  border-radius: var(--radius-pill);
286
295
  position: relative;
287
296
  overflow: hidden;
@@ -74,8 +74,8 @@
74
74
  }
75
75
  .sb-item:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
76
76
  .sb-item.active {
77
- background: var(--color-brand-secondary-50);
78
- color: var(--color-brand-secondary-700);
77
+ background: var(--color-background-selected);
78
+ color: var(--color-foreground-brand);
79
79
  font-weight: var(--w-semibold);
80
80
  }
81
81
  .sb-item.active::before {
@@ -168,7 +168,7 @@
168
168
  }
169
169
  .sb-children .sb-subitem:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
170
170
  .sb-children .sb-subitem:hover::before { opacity: 1; background: var(--color-foreground-muted); }
171
- .sb-children .sb-subitem.active { color: var(--color-brand-secondary-700); font-weight: var(--w-semibold); background: var(--color-brand-secondary-50); }
171
+ .sb-children .sb-subitem.active { color: var(--color-foreground-brand); font-weight: var(--w-semibold); background: var(--color-background-selected); }
172
172
  .sb-children .sb-subitem.active::before { background: var(--color-brand-secondary-500); opacity: 1; }
173
173
  .sb-children .sb-subitem .count, .sb-children .sb-subitem .sb-count {
174
174
  margin-left: auto;
@@ -0,0 +1,91 @@
1
+ /* ============================================================
2
+ SMART SEARCH — global command palette (⌘K) shell
3
+ Promoted from combobox.html §03 (2026-06-11): the 8 canonical
4
+ states (inactive · active · word · similar · id · partial-id ·
5
+ no-results · loading · error) compose from these pieces.
6
+ Consumed by the SmartSearch TSX component.
7
+ ============================================================ */
8
+ /* Smart-search combobox */
9
+ .ss { width: 100%; max-width: 380px; background: var(--color-background-surface); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); box-shadow: var(--shadow-sm); overflow: hidden; font-size: var(--fs-sm); }
10
+ .ss-input { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border-subtle); }
11
+ .ss-input.ss-input-flat { border-bottom: 0; }
12
+ .ss-input svg, .ss-input i { width: var(--icon-sm); height: var(--icon-sm); stroke-width: var(--icon-stroke); color: var(--color-foreground-muted); flex-shrink: 0; }
13
+ .ss-input input { flex: 1; border: 0; outline: 0; background: transparent; font: inherit; color: var(--color-foreground-default); }
14
+ .ss-input input::placeholder { color: var(--color-foreground-placeholder); }
15
+ .ss-clear { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border: 0; padding: 0; border-radius: var(--radius-pill); background: transparent; color: var(--color-foreground-muted); cursor: pointer; }
16
+ .ss-clear:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
17
+ .ss-clear svg, .ss-clear i { width: 12px; height: 12px; }
18
+
19
+ .ss-section { padding: var(--space-3) var(--space-2); border-bottom: 1px solid var(--color-border-subtle); }
20
+ .ss-section:last-of-type { border-bottom: 0; }
21
+ .ss-section-label { font-size: var(--fs-xxs); font-weight: var(--w-semibold); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--color-foreground-muted); padding: 0 var(--space-2); margin-bottom: var(--space-2); }
22
+
23
+ /* Suggestion chips (active state) */
24
+ .ss-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 var(--space-2); }
25
+ .ss-chip { display: inline-flex; align-items: center; gap: var(--space-2); padding: 4px var(--space-3); background: var(--color-background-canvas); border: 1px solid var(--color-border-default); border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: var(--w-medium); color: var(--color-foreground-default); cursor: pointer; transition: var(--transition-fast); }
26
+ .ss-chip:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); }
27
+ .ss-chip svg, .ss-chip i { width: 11px; height: 11px; stroke-width: var(--icon-stroke); color: var(--color-foreground-muted); }
28
+
29
+ /* List rows */
30
+ .ss-list { list-style: none; margin: 0; padding: 0; }
31
+ .ss-list-item { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); cursor: pointer; transition: var(--transition-fast); }
32
+ .ss-list-item:hover, .ss-list-item.is-active { background: var(--color-background-hover); }
33
+ .ss-list-item .icon-leading { width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--color-background-canvas); display: inline-flex; align-items: center; justify-content: center; color: var(--color-foreground-secondary); flex-shrink: 0; }
34
+ .ss-list-item .icon-leading svg, .ss-list-item .icon-leading i { width: 14px; height: 14px; stroke-width: var(--icon-stroke); }
35
+ .ss-list-item .icon-leading.brand { background: var(--color-brand-secondary-50); color: var(--color-brand-secondary-700); }
36
+ .ss-list-item .text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
37
+ .ss-list-item .text .label { font-size: var(--fs-sm); font-weight: var(--w-semibold); color: var(--color-foreground-default); }
38
+ .ss-list-item .text .meta { font-size: var(--fs-xs); color: var(--color-foreground-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
39
+ .ss-list-item .icon-trailing { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; flex-shrink: 0; color: var(--color-foreground-muted); }
40
+ .ss-list-item .icon-trailing svg, .ss-list-item .icon-trailing i { width: 14px; height: 14px; stroke-width: var(--icon-stroke); }
41
+
42
+ /* Recent searches — clock icon + dim text */
43
+ .ss-recent .ss-list-item .icon-leading { background: transparent; border: 0; width: 18px; height: 18px; }
44
+ .ss-recent .ss-list-item .icon-leading svg, .ss-recent .ss-list-item .icon-leading i { width: 13px; height: 13px; color: var(--color-foreground-muted); }
45
+ .ss-recent .ss-list-item .text .label { font-weight: var(--w-regular); color: var(--color-foreground-secondary); }
46
+
47
+ /* Empty/result label */
48
+ .ss-result-label { padding: 0 var(--space-2) var(--space-2); }
49
+ .ss-result-label .l { font-size: var(--fs-xxs); font-weight: var(--w-semibold); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--color-foreground-muted); }
50
+ .ss-no-result { padding: 0 var(--space-2); font-size: var(--fs-sm); color: var(--color-foreground-muted); }
51
+ .ss-no-result strong { color: var(--color-foreground-default); font-weight: var(--w-semibold); }
52
+
53
+ /* Tip / hint card (similar match suggestion, ID format hint) */
54
+ .ss-hint { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); background: var(--color-background-subtle); border: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm); margin: var(--space-3) var(--space-2) 0; }
55
+ .ss-hint .icon-leading { color: var(--color-foreground-muted); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; }
56
+ .ss-hint .icon-leading svg, .ss-hint .icon-leading i { width: 14px; height: 14px; stroke-width: var(--icon-stroke); }
57
+ .ss-hint .body { font-size: var(--fs-sm); color: var(--color-foreground-secondary); line-height: var(--lh-relaxed); }
58
+ .ss-hint .body strong { color: var(--color-foreground-default); font-weight: var(--w-semibold); }
59
+ .ss-hint .body code { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--color-foreground-accent); background: color-mix(in srgb, var(--color-brand-secondary-500) 12%, transparent); padding: 1px var(--space-2); border-radius: var(--radius-xs); }
60
+
61
+ /* ID type-picker buttons */
62
+ .ss-typebtns { display: flex; flex-direction: column; gap: var(--space-2); padding: 0 var(--space-2); }
63
+ .ss-typebtn { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-3); background: var(--color-background-canvas); border: 1px solid var(--color-border-default); border-radius: var(--radius-sm); font-size: var(--fs-sm); font-weight: var(--w-medium); color: var(--color-foreground-default); cursor: pointer; transition: var(--transition-fast); text-align: left; width: 100%; }
64
+ .ss-typebtn:hover { background: var(--color-background-hover); border-color: var(--color-border-strong); }
65
+ .ss-typebtn svg, .ss-typebtn i { width: 16px; height: 16px; stroke-width: var(--icon-stroke); color: var(--color-foreground-muted); }
66
+ .ss-typebtn .grow { flex: 1; }
67
+ .ss-typebtn .chev { color: var(--color-foreground-muted); }
68
+
69
+ /* Loading skeleton + spinner illustration */
70
+ .ss-skeleton { padding: var(--space-3) var(--space-2); display: flex; flex-direction: column; gap: var(--space-3); }
71
+ .ss-skel-row { display: flex; align-items: center; gap: var(--space-3); }
72
+ .ss-skel-circle { width: 28px; height: 28px; border-radius: var(--radius-sm); background: var(--color-border-default); flex-shrink: 0; }
73
+ .ss-skel-bars { flex: 1; display: flex; flex-direction: column; gap: 5px; }
74
+ .ss-skel-bar { height: 8px; border-radius: var(--radius-xs); background: var(--color-border-default); }
75
+ .ss-skel-bar.short { width: 40%; }
76
+ .ss-skel-bar.long { width: 70%; }
77
+ .ss-skel-shimmer { background: linear-gradient(90deg, var(--color-border-default) 0%, var(--color-border-subtle) 50%, var(--color-border-default) 100%); background-size: 200% 100%; animation: skel 1.4s linear infinite; }
78
+ @keyframes skel { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
79
+
80
+ .ss-illo { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); padding: var(--space-7) var(--space-5); text-align: center; }
81
+ .ss-illo svg.illustration { width: 84px; height: 60px; }
82
+ .ss-illo .title { font-size: var(--fs-md); font-weight: var(--w-semibold); color: var(--color-foreground-default); }
83
+ .ss-illo .body { font-size: var(--fs-xs); color: var(--color-foreground-muted); max-width: 32ch; line-height: var(--lh-relaxed); }
84
+
85
+ /* Footer with keyboard shortcuts */
86
+ .ss-foot { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-2) var(--space-3); background: var(--color-background-subtle); border-top: 1px solid var(--color-border-subtle); font-size: var(--fs-xxs); color: var(--color-foreground-muted); }
87
+ .ss-foot .kbd-group { display: inline-flex; align-items: center; gap: 4px; }
88
+ kbd.ss-kbd { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 4px; background: var(--color-background-surface); border: 1px solid var(--color-border-default); border-radius: var(--radius-xs); font-family: var(--font-mono); font-size: 10px; font-weight: var(--w-semibold); color: var(--color-foreground-secondary); }
89
+ .ss-foot .sep { width: 1px; height: 12px; background: var(--color-border-default); }
90
+
91
+
@@ -138,9 +138,9 @@
138
138
  .dt-pagination .page-btn:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
139
139
  .dt-pagination .page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
140
140
  .dt-pagination .page-btn.is-current {
141
- background: var(--color-brand-accent-50);
142
- border-color: var(--color-brand-accent-200);
143
- color: var(--color-brand-accent-700);
141
+ background: color-mix(in srgb, var(--color-brand-accent-500) 14%, transparent);
142
+ border-color: color-mix(in srgb, var(--color-brand-accent-500) 36%, transparent);
143
+ color: var(--color-foreground-accent);
144
144
  }
145
145
  .dt-pagination .page-btn > i, .dt-pagination .page-btn > svg,
146
146
  .dt-pagination .page-btn > svg { width: 12px; height: 12px; stroke-width: var(--icon-stroke); }
@@ -218,8 +218,8 @@
218
218
  .dt tbody tr.is-muted td,
219
219
  .dt tbody tr.is-muted .dt-cell-stack > .sub { opacity: 0.55; }
220
220
 
221
- .dt tbody tr.is-error { background: color-mix(in srgb, var(--color-danger-50) 60%, transparent); }
222
- .dt tbody tr.is-error:hover { background: color-mix(in srgb, var(--color-danger-50) 100%, transparent); }
221
+ .dt tbody tr.is-error { background: color-mix(in srgb, var(--color-feedback-danger-background) 60%, transparent); }
222
+ .dt tbody tr.is-error:hover { background: var(--color-feedback-danger-background); }
223
223
 
224
224
 
225
225
  /* ----------------------------------------------------------------
@@ -437,11 +437,13 @@
437
437
  display: inline-block;
438
438
  height: 12px;
439
439
  border-radius: var(--radius-2xs);
440
+ /* theme-aware: border tokens are light-gray hairlines in light and
441
+ white-alpha in dark — raw neutral-50/100 read near-white on ink */
440
442
  background:
441
443
  linear-gradient(90deg,
442
- var(--color-neutral-100) 0%,
443
- var(--color-neutral-50) 40%,
444
- var(--color-neutral-100) 80%);
444
+ var(--color-border-default) 0%,
445
+ var(--color-border-subtle) 40%,
446
+ var(--color-border-default) 80%);
445
447
  background-size: 480px 100%;
446
448
  animation: dt-shimmer 1.4s var(--ease-linear) infinite;
447
449
  }
@@ -83,8 +83,8 @@
83
83
  }
84
84
  .tab[aria-selected="true"] .tab-count,
85
85
  .tab.is-active .tab-count {
86
- background: var(--color-brand-accent-50);
87
- color: var(--color-brand-accent-700);
86
+ background: color-mix(in srgb, var(--color-brand-accent-500) 16%, transparent);
87
+ color: var(--color-foreground-accent);
88
88
  }
89
89
 
90
90
  /* Pill tabs — alternative for compact, segmented contexts */
@@ -130,6 +130,10 @@
130
130
  to { transform: scaleX(0); }
131
131
  }
132
132
 
133
+ /* Auto-dismiss must pause while the user inspects (doc a11y contract) */
134
+ .toast:hover .toast-progress::after,
135
+ .toast:focus-within .toast-progress::after { animation-play-state: paused; }
136
+
133
137
  /* ============================================================
134
138
  VARIANTS — semantic
135
139
  ============================================================ */
@@ -220,6 +220,11 @@
220
220
  line-height: 1;
221
221
  }
222
222
 
223
+ /* Esc suppression — hide while focus stays on the target (doc a11y contract).
224
+ The TSX toggles .is-suppressed on Escape; clears on blur / mouse re-enter. */
225
+ .tooltip-target.is-suppressed > .tooltip,
226
+ .tooltip-target.is-suppressed > .tooltip-rich { opacity: 0; pointer-events: none; }
227
+
223
228
  /* Reduced motion */
224
229
  @media (prefers-reduced-motion: reduce) {
225
230
  .tooltip { transition: opacity var(--duration-instant) linear; transform: none !important; transition-delay: 0s !important; }
@@ -14,6 +14,7 @@
14
14
  @import "./components/overlays.css";
15
15
  @import "./components/primitives.css";
16
16
  @import "./components/sidebar.css";
17
+ @import "./components/smart-search.css";
17
18
  @import "./components/status-pills.css";
18
19
  @import "./components/stepper.css";
19
20
  @import "./components/table.css";
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+
3
+ export interface SidebarChildItem {
4
+ key: string;
5
+ label: React.ReactNode;
6
+ count?: number | string;
7
+ }
8
+
9
+ export interface SidebarItem {
10
+ key: string;
11
+ label: React.ReactNode;
12
+ /** 15px icon slot (.ic). */
13
+ icon?: React.ReactNode;
14
+ /** Status badge text (e.g. "NEW") — mutually exclusive with count. */
15
+ badge?: string;
16
+ badgeTone?: 'alert' | 'warning';
17
+ /** Numeric trailing count. */
18
+ count?: number | string;
19
+ disabled?: boolean;
20
+ /** Sub-items — renders a collapsible .sb-group. */
21
+ children?: SidebarChildItem[];
22
+ }
23
+
24
+ export interface SidebarSection {
25
+ /** Uppercase section label; omit for the first unlabelled block. */
26
+ label?: React.ReactNode;
27
+ items: SidebarItem[];
28
+ }
29
+
30
+ export interface AppSidebarProps {
31
+ sections: SidebarSection[];
32
+ /** Key of the active item/sub-item. */
33
+ active?: string;
34
+ onNav?: (key: string) => void;
35
+ /** Full-height app rail (.sb-app) instead of card framing. */
36
+ app?: boolean;
37
+ /** 60px icon rail. */
38
+ collapsed?: boolean;
39
+ /** Renders the header collapse toggle. */
40
+ onToggleCollapse?: () => void;
41
+ /** Header brand slot (logo img). */
42
+ logo?: React.ReactNode;
43
+ /** Footer slot (.sb-foot markup is the caller's). */
44
+ foot?: React.ReactNode;
45
+ className?: string;
46
+ style?: React.CSSProperties;
47
+ }
48
+
49
+ export declare const AppSidebar: React.FC<AppSidebarProps>;
50
+ export default AppSidebar;