@plastic-js/tsumiki 0.1.52 → 0.1.53

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 (57) hide show
  1. package/dist/components/Accordion.js +6 -6
  2. package/dist/components/Avatar.js +4 -4
  3. package/dist/components/BackToTop.js +3 -3
  4. package/dist/components/BottomNavigation.js +4 -4
  5. package/dist/components/BottomSheet.js +1 -1
  6. package/dist/components/Button.js +17 -19
  7. package/dist/components/Checkbox.js +11 -29
  8. package/dist/components/Clipboard.js +2 -2
  9. package/dist/components/CloseButton.js +4 -4
  10. package/dist/components/Collapsible.js +3 -3
  11. package/dist/components/ColorPicker.js +6 -6
  12. package/dist/components/Combobox.js +10 -9
  13. package/dist/components/{DateInput.js → DatePicker.js} +37 -22
  14. package/dist/components/Dialog.js +7 -7
  15. package/dist/components/Drawer.js +7 -7
  16. package/dist/components/Field.js +3 -3
  17. package/dist/components/Fieldset.js +2 -2
  18. package/dist/components/FileUpload.js +12 -12
  19. package/dist/components/FilterGroup.js +50 -115
  20. package/dist/components/Input.js +15 -14
  21. package/dist/components/Link.js +2 -2
  22. package/dist/components/Listbox.js +7 -6
  23. package/dist/components/Menu.js +8 -7
  24. package/dist/components/MoneyInput.js +1 -1
  25. package/dist/components/NumberInput.js +8 -9
  26. package/dist/components/Pagination.js +8 -8
  27. package/dist/components/Popover.js +5 -5
  28. package/dist/components/Progress.js +3 -3
  29. package/dist/components/RadioGroup.js +5 -10
  30. package/dist/components/RatingGroup.js +5 -5
  31. package/dist/components/SearchInput.js +4 -1
  32. package/dist/components/Select.js +51 -25
  33. package/dist/components/SelectPc.js +18 -20
  34. package/dist/components/SignaturePad.js +6 -7
  35. package/dist/components/Skeleton.js +1 -1
  36. package/dist/components/Slider.js +7 -7
  37. package/dist/components/Spinner.js +1 -1
  38. package/dist/components/Steps.js +23 -23
  39. package/dist/components/Switch.js +19 -10
  40. package/dist/components/Tabs.js +5 -5
  41. package/dist/components/TagsInput.js +6 -6
  42. package/dist/components/Toast.js +14 -14
  43. package/dist/components/Toggle.js +7 -7
  44. package/dist/components/ToggleGroup.js +19 -8
  45. package/dist/components/Tour.js +3 -3
  46. package/dist/components/TreeView.js +5 -5
  47. package/dist/index.js +2 -2
  48. package/dist/tsumiki.css +1 -1
  49. package/docs/api.md +47 -135
  50. package/docs/component-api-pattern.md +1 -1
  51. package/docs/scroll-lock-issue.md +3 -3
  52. package/package.json +1 -1
  53. package/src/styles/color.css +42 -0
  54. package/src/styles/semantic.css +212 -0
  55. package/src/styles/shadow.css +1 -0
  56. package/src/styles/theme.css +14 -0
  57. package/src/styles/tokens.css +1 -0
package/docs/api.md CHANGED
@@ -48,6 +48,44 @@ The shared `size` prop accepts: `xs | sm | md | lg | xl | xxl` (default `md`).
48
48
 
49
49
  ---
50
50
 
51
+ ## Semantic tokens
52
+
53
+ The design system exposes a **semantic color layer** (`src/styles/semantic.css`) between the raw Radix palette steps and the components. Components reference *semantics*, never raw steps, so a style stays consistent across light/dark mode and across containers (page vs. sheet/popover).
54
+
55
+ **Background hierarchy** — container-aware:
56
+
57
+ | Token | Value | Use |
58
+ |---|---|---|
59
+ | `--tsu-bg-page` | `neutral-1` | App page background |
60
+ | `--tsu-bg-panel` | `neutral-2` | Cards, sheets, popovers, dialogs |
61
+ | `--tsu-bg-control` | `neutral-3` | Form controls (inputs, buttons) |
62
+ | `--tsu-bg-control-hover` | `neutral-4` | Hover of raised controls |
63
+ | `--tsu-bg-overlay` | `rgba(0,0,0,0.5)` | Modal scrim |
64
+ | `--tsu-bg-overlay-soft` | `rgba(0,0,0,0.35)` | Sheet / bottom scrim |
65
+
66
+ **Text hierarchy:** `--tsu-text-primary` (`neutral-12`), `--tsu-text-secondary` (`neutral-11`), `--tsu-text-subtle` (`neutral-9`).
67
+
68
+ **Palette variants** — every palette (`neutral`, `accent`, `danger`, `success`, `warning`) exposes the same five variants. Each variant provides `-bg`, `-bg-hover`, `-bg-active`, `-border`, `-border-hover`, `-fg` as applicable:
69
+
70
+ | Variant | Pattern | Example |
71
+ |---|---|---|
72
+ | `solid` | Filled control, high contrast | `--tsu-accent-solid-bg` = `accent-9`, `--tsu-accent-solid-fg` = white |
73
+ | `surface` | Panel-tinted control with border | `--tsu-accent-surface-bg` = `accent-alpha-2`, `--tsu-accent-surface-border` = `accent-alpha-6` |
74
+ | `subtle` | Tinted control, no border | `--tsu-accent-subtle-bg` = `accent-alpha-3` |
75
+ | `outline` | Transparent control with border | `--tsu-accent-outline-border` = `accent-alpha-7` |
76
+ | `plain` | Text-like control | `--tsu-accent-plain-fg` = `accent-11` |
77
+
78
+ **Hover/active tints use ALPHA tokens** (e.g. `--tsu-accent-alpha-3`), so they read identically over the page background and over panel/sheet backgrounds.
79
+
80
+ **States:**
81
+
82
+ - **Pressed/active** — unified via `--tsu-pressed` / `--tsu-pressed-hover` (neutral alpha) and `--tsu-pressed-accent` / `--tsu-pressed-accent-hover` (accent alpha).
83
+ - **Disabled** — a uniform state applied on top of any variant: `--tsu-disabled-opacity` (`0.5`) fades the whole component (no grayscale), keeping the `neutral` intent (gray) visually distinct from the accent intent even when disabled.
84
+
85
+ **Elevation** — `--tsu-shadow-sm / md / lg` (downward) and `--tsu-shadow-up` (upward, for bottom sheets), all composed from neutral alpha tokens.
86
+
87
+ ---
88
+
51
89
  ## Inputs & Forms
52
90
 
53
91
  ### Field
@@ -136,143 +174,16 @@ Currency-formatted numeric input.
136
174
  **Exports:** `NumberInput` · Escape hatch: `NumberInput.origin.{Root, DecrementTrigger, Input, IncrementTrigger}`
137
175
 
138
176
  | Prop | Type | Default | Description |
139
- |---|---|---|---|
140
- | `size` | `string` | `md` | See size values. |
141
- | `solid` | `boolean` | `false` | Filled background instead of outlined. |
142
- | `disabled` | `boolean` | `false` | Disabled. |
143
- | `children` | `node` | — | Custom triggers, if any. |
144
-
145
- Passthrough: Ark `Root` props (`value`, `onValueChange`, `min`, `max`, `step`, `formatOptions`, …).
146
-
147
- ### SearchInput
148
-
149
- **Exports:** `SearchInput`
150
-
151
- | Prop | Type | Default | Description |
152
- |---|---|---|---|
153
- | `placeholder` | `string` | — | Placeholder text. |
154
- | `onInput` | `(value) => void` | — | Called with the current query. |
155
-
156
- ### TagsInput
157
-
158
- **Exports:** `TagsInput` · Escape hatch: `TagsInput.origin.{Root, Label, Control, Input, Item, ItemPreview, ItemText, ItemDeleteTrigger, ItemInput, ClearTrigger, HiddenInput}`
159
-
160
- | Prop | Type | Default | Description |
161
- |---|---|---|---|
162
- | `className` | `string` | — | Root class. |
163
- | `label` | `string` | — | Optional label text. |
164
- | `placeholder` | `string` | — | Input placeholder. |
165
- | `children` | `node` | — | Custom item rendering. |
166
-
167
- Passthrough: Ark `Root` props (`value`, `onValueChange`, `allowOverflow`, `validate`, …).
168
-
169
- ### DateInput
170
-
171
- Mobile date picker built on the `DateWheel` utility.
172
-
173
- **Exports:** `DateInput`
174
-
175
- | Prop | Type | Default | Description |
176
- |---|---|---|---|
177
- | `value` | `string \| (() => string)` | — | ISO date string. |
178
- | `onValueChange` | `(value) => void` | — | Called with the new ISO date. |
179
- | `placeholder` | `string` | — | Placeholder text. |
180
- | `disabled` | `boolean` | `false` | Disabled. |
181
- | `size` | `string` | `md` | See size values. |
182
- | `className` | `string` | — | Root class. |
183
-
184
- ### ColorPicker
185
-
186
- **Exports:** `ColorPicker` · Escape hatch: `ColorPicker.origin.*` (full Ark surface: `Root`, `Label`, `Control`, `Trigger`, `Positioner`, `Content`, `ValueSwatch`, `ValueText`, `Area`, `ChannelSlider`, `ChannelInput`, `TransparencyGrid`, `EyeDropperTrigger`, …)
187
-
188
- | Prop | Type | Default | Description |
189
- |---|---|---|---|
190
- | `label` | `string` | — | Optional label text. |
191
- | `showAlpha` | `boolean` | — | Show the alpha channel. |
192
- | `eyedropper` | `boolean` | — | Show the eyedropper trigger. |
193
- | `hiddenInput` | `boolean` | — | Render a hidden input. |
194
- | `onChange` | `(value) => void` | — | Called on color change. |
195
- | `size` | `string` | `md` | See size values. |
196
- | `className` | `string` | — | Root class. |
197
- | `children` | `node` | — | Custom content. |
198
-
199
- Passthrough: Ark `Root` props (`value`, `onValueChange`, `format`, …).
200
-
201
- ### Clipboard
202
-
203
- **Exports:** `Clipboard`
204
-
205
- | Prop | Type | Default | Description |
206
- |---|---|---|---|
207
- | `size` | `string` | `md` | See size values. |
208
- | `copiedDuration` | `number` | `2000` | ms to keep the "copied" state after copying. |
209
-
210
- Passthrough: Ark `Root` props (`value`, `onValueChange`, …).
211
-
212
- ---
213
-
214
- ## Buttons & Toggles
215
-
216
- ### Button
217
-
218
- **Exports:** `Button` · Escape hatch: `Button.origin.Root`
219
-
220
- | Prop | Type | Default | Description |
221
- |---|---|---|---|
222
- | `solid` | `boolean` | `false` | Filled (solid) variant instead of outline. |
223
- | `intent` | `string` | `'default'` | `'default'`, `'neutral'`, or `'danger'`. `neutral` applies only to the outline variant (ignored when `solid`). |
224
- | `size` | `string` | `md` | See size values. |
225
- | `disabled` | `boolean` | `false` | Disabled. |
226
- | `loading` | `boolean` | `false` | Shows a spinner and blocks interaction. |
227
- | `children` | `node` | — | Button content. |
228
-
229
- ### Checkbox
230
-
231
- **Exports:** `Checkbox` · Escape hatch: `Checkbox.origin.{Root, Label, Control, Indicator, HiddenInput, Group}`
232
-
233
- | Prop | Type | Default | Description |
234
- |---|---|---|---|
235
- | `size` | `string` | `md` | See size values. |
236
- | `rounded` | `boolean` | `true` | Rounded (pill) control shape. |
237
- | `invalid` | `boolean` | `false` | Error state. |
238
- | `children` | `node` | — | Label content. |
239
-
240
- Passthrough: Ark `Root` props (`checked`, `onCheckedChange`, `disabled`, `indeterminate`, …).
241
-
242
- ### Switch
243
-
244
- **Exports:** `Switch` · Escape hatch: `Switch.origin.{Root, Control, Thumb, Label, HiddenInput}`
245
-
246
- | Prop | Type | Default | Description |
247
- |---|---|---|---|
248
- | `size` | `string` | `md` | See size values. |
249
- | `children` | `node` | — | Label content. |
250
-
251
- Passthrough: Ark `Root` props (`checked`, `onCheckedChange`, `disabled`, …).
252
-
253
- ### Toggle
254
-
255
- **Exports:** `Toggle` · Escape hatch: `Toggle.origin.Root`
256
-
257
- | Prop | Type | Default | Description |
258
- |---|---|---|---|
259
- | `className` | `string` | — | Root class. |
260
- | `children` | `node` | — | Content. |
261
-
262
- Passthrough: Ark `Root` props (`pressed`, `onPressedChange`, …).
263
-
264
- ### ToggleGroup
265
-
266
- **Exports:** `ToggleGroup`, `ToggleGroupItem` · Escape hatch: `ToggleGroup.origin.{Root, Item}`
267
-
268
- | Prop | Type | Default | Description |
269
- |---|---|---|---|
177
+ |---|---|---|---|---|
270
178
  | `size` | `string` | `md` | See size values. |
179
+ | `intent` | `'default' \| 'neutral'` | `'default'` | Border color of the group. `neutral` uses the neutral outline border (matching Input's neutral); the checked segment keeps the accent background. |
271
180
  | `children` | `node` | — | `ToggleGroupItem` elements. |
272
181
 
273
182
  ### FilterGroup
274
183
 
275
- Multi-select filter bar built on `ToggleGroup`. Renders an "All" toggle; selecting any item clears it, and an empty selection maps back to "All".
184
+ Multi-select filter bar built on the `Button` component. Renders an "All" pill (outline when idle, solid accent when active); item pills are neutral when idle and accent-outline when selected. Selecting any item clears "All", and an empty selection maps back to "All".
185
+
186
+ > **Design note:** `FilterGroup` renders its pills with `Button` (not `ToggleGroup`) to reuse Button's variant/intent styling and the neutral/outline/solid state language. Trade-off: the multi-select toggle logic is reimplemented here instead of delegating to `ToggleGroup`'s selection machinery. `ToggleGroup` remains available as a standalone segmented control.
276
187
 
277
188
  **Exports:** `FilterGroup`
278
189
 
@@ -284,7 +195,7 @@ Multi-select filter bar built on `ToggleGroup`. Renders an "All" toggle; selecti
284
195
  | `allLabel` | `string` | `'All'` | Label for the clear/All toggle. |
285
196
  | `size` | `string` | `md` | See size values. |
286
197
  | `buttonClass` | `string` | — | Class applied to every button. |
287
- | `groupClass` | `string` | — | Class applied to the inner `ToggleGroup`. |
198
+ | `groupClass` | `string` | — | Class applied to the inner scroll container. |
288
199
  | `sticky` | `boolean \| number` | — | Sticky to the top of the scroll container; pass a number for a `top` offset in px. |
289
200
  | `className` | `string` | — | Root class. |
290
201
 
@@ -325,13 +236,14 @@ Mobile-optimized bottom-sheet select (draggable sheet overlay).
325
236
  |---|---|---|---|
326
237
  | `value` | `string \| (() => string)` | — | Selected value. |
327
238
  | `onValueChange` | `(value) => void` | — | Called when an item is selected. |
328
- | `items` | `array \| (() => array)` | `[]` | Data array. |
239
+ | `items` | `array \| (() => array)` | `[]` | Data array. Items may include `disabled: true` to render a non-selectable row. |
329
240
  | `itemToValue` | `(item) => string` | `item.value` | Extract value from an item. |
330
241
  | `itemToLabel` | `(item) => node` | `item.label` | Extract label from an item. |
331
242
  | `open` | `(() => boolean)` | — | **Required** controlled open state. |
332
243
  | `onOpenChange` | `(open) => void` | — | Open/close callback. |
333
244
  | `disabled` | `boolean` | `false` | Disabled. |
334
245
  | `solid` | `boolean` | `false` | Filled trigger variant instead of outline. |
246
+ | `intent` | `'default' \| 'neutral' \| 'danger'` | `'default'` | Border color for trigger and the search input. `neutral` applies only to the outline variant (ignored when `solid`). |
335
247
  | `filter` | `(item, query, itemToLabel) => boolean` | — | Custom filter for the search box. |
336
248
  | `placeholder` | `string` | — | Trigger placeholder. |
337
249
  | `backdropClassName` | `string` | — | Backdrop class. |
@@ -685,7 +597,7 @@ Passthrough: Ark `Root` props (`value`, `min`, `max`, …).
685
597
  > **General rule: always use the highest-level callback you need.** Reach for `onCancel` (and `onConfirm`) first — they express intent. Only fall back to `onClose` when you need close cleanup for every path, and to `onOpenChange` only when you need raw open/close control. Passing a lower-level callback just because a higher-level one exists usually means you are bypassing the intent layer.
686
598
 
687
599
  **Known issues:**
688
- - Pressing <kbd>Escape</kbd> closes both the Dialog and any nested overlay (e.g. `DateInput`, `Select`) that is open inside it. There is currently no event coordination between the two — both react to the same key press.
600
+ - Pressing <kbd>Escape</kbd> closes both the Dialog and any nested overlay (e.g. `DatePicker`, `Select`) that is open inside it. There is currently no event coordination between the two — both react to the same key press.
689
601
 
690
602
  ### ConfirmDialog
691
603
 
@@ -74,7 +74,7 @@ Whether the component is built on top of a same-named Ark primitive, or implemen
74
74
  | Clipboard | Web Clipboard API |
75
75
  | CloseButton | `@emotion/css` |
76
76
  | ConfirmDialog | Composes `Dialog` |
77
- | DateInput | `DateWheel` utility |
77
+ | DatePicker | `DateWheel` utility |
78
78
  | Icon | `@emotion/css` |
79
79
  | MoneyInput | Composes `Input` |
80
80
  | SearchInput | Composes `Input` |
@@ -1,7 +1,7 @@
1
1
  # Scroll-lock is not stacked
2
2
 
3
3
  **Status:** Known issue
4
- **Affects:** `Select.jsx:193`, `DateInput.jsx:293/303`
4
+ **Affects:** `Select.jsx:193`, `DatePicker.jsx:293/303`
5
5
 
6
6
  ## Problem
7
7
 
@@ -11,7 +11,7 @@ Overlay components set `document.body.style.overflow` directly:
11
11
  // Select
12
12
  document.body.style.overflow = v ? 'hidden' : ''
13
13
 
14
- // DateInput
14
+ // DatePicker
15
15
  document.body.style.overflow = 'hidden' // on open
16
16
  document.body.style.overflow = '' // on close
17
17
  ```
@@ -31,5 +31,5 @@ const lock = () => { count += 1; document.body.style.overflow = 'hidden' }
31
31
  const unlock = () => { count = Math.max(0, count - 1); if (count === 0) document.body.style.overflow = '' }
32
32
  ```
33
33
 
34
- Use it in every overlay (`Select`, `DateInput`, …) instead of writing
34
+ Use it in every overlay (`Select`, `DatePicker`, …) instead of writing
35
35
  `document.body.style.overflow` directly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plastic-js/tsumiki",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "A UI component library for Plastic JS.",
5
5
  "license": "MIT",
6
6
  "author": "tigre",
@@ -62,6 +62,20 @@
62
62
  --tsu-danger-11: var(--red-11);
63
63
  --tsu-danger-12: var(--red-12);
64
64
 
65
+ /* Danger alpha (red) */
66
+ --tsu-danger-alpha-1: var(--red-a1);
67
+ --tsu-danger-alpha-2: var(--red-a2);
68
+ --tsu-danger-alpha-3: var(--red-a3);
69
+ --tsu-danger-alpha-4: var(--red-a4);
70
+ --tsu-danger-alpha-5: var(--red-a5);
71
+ --tsu-danger-alpha-6: var(--red-a6);
72
+ --tsu-danger-alpha-7: var(--red-a7);
73
+ --tsu-danger-alpha-8: var(--red-a8);
74
+ --tsu-danger-alpha-9: var(--red-a9);
75
+ --tsu-danger-alpha-10: var(--red-a10);
76
+ --tsu-danger-alpha-11: var(--red-a11);
77
+ --tsu-danger-alpha-12: var(--red-a12);
78
+
65
79
  /* Success (green) */
66
80
  --tsu-success-1: var(--green-1);
67
81
  --tsu-success-2: var(--green-2);
@@ -76,6 +90,20 @@
76
90
  --tsu-success-11: var(--green-11);
77
91
  --tsu-success-12: var(--green-12);
78
92
 
93
+ /* Success alpha (green) */
94
+ --tsu-success-alpha-1: var(--green-a1);
95
+ --tsu-success-alpha-2: var(--green-a2);
96
+ --tsu-success-alpha-3: var(--green-a3);
97
+ --tsu-success-alpha-4: var(--green-a4);
98
+ --tsu-success-alpha-5: var(--green-a5);
99
+ --tsu-success-alpha-6: var(--green-a6);
100
+ --tsu-success-alpha-7: var(--green-a7);
101
+ --tsu-success-alpha-8: var(--green-a8);
102
+ --tsu-success-alpha-9: var(--green-a9);
103
+ --tsu-success-alpha-10: var(--green-a10);
104
+ --tsu-success-alpha-11: var(--green-a11);
105
+ --tsu-success-alpha-12: var(--green-a12);
106
+
79
107
  /* Warning (amber) */
80
108
  --tsu-warning-1: var(--amber-1);
81
109
  --tsu-warning-2: var(--amber-2);
@@ -90,6 +118,20 @@
90
118
  --tsu-warning-11: var(--amber-11);
91
119
  --tsu-warning-12: var(--amber-12);
92
120
 
121
+ /* Warning alpha (amber) */
122
+ --tsu-warning-alpha-1: var(--amber-a1);
123
+ --tsu-warning-alpha-2: var(--amber-a2);
124
+ --tsu-warning-alpha-3: var(--amber-a3);
125
+ --tsu-warning-alpha-4: var(--amber-a4);
126
+ --tsu-warning-alpha-5: var(--amber-a5);
127
+ --tsu-warning-alpha-6: var(--amber-a6);
128
+ --tsu-warning-alpha-7: var(--amber-a7);
129
+ --tsu-warning-alpha-8: var(--amber-a8);
130
+ --tsu-warning-alpha-9: var(--amber-a9);
131
+ --tsu-warning-alpha-10: var(--amber-a10);
132
+ --tsu-warning-alpha-11: var(--amber-a11);
133
+ --tsu-warning-alpha-12: var(--amber-a12);
134
+
93
135
  /* Neutral Alpha Overlay (used for shadows/elevation) */
94
136
  --tsu-neutral-alpha-1: var(--olive-a1);
95
137
  --tsu-neutral-alpha-2: var(--olive-a2);
@@ -0,0 +1,212 @@
1
+ /*
2
+ * Semantic token layer.
3
+ *
4
+ * Sits between the raw palette steps (theme.css / color.css) and the
5
+ * components. Components reference *semantics* (`--tsu-accent-solid-bg`,
6
+ * `--tsu-neutral-outline-border`, …), never raw steps — so a style survives
7
+ * wherever it is mounted and dark mode flips everything consistently.
8
+ *
9
+ * Modeled on Park UI / Radix Themes:
10
+ * - every palette has the same 5 variants: solid · surface · subtle ·
11
+ * outline · plain, each with bg / bg-hover / bg-active / border / fg
12
+ * - hover/active tints are ALPHA tokens, so they read identically over the
13
+ * page background and over panel/sheet backgrounds
14
+ * - `disabled` is a uniform 50% opacity state applied on top of any variant
15
+ * — never a separate near-identical gray color
16
+ */
17
+
18
+ :root {
19
+ /* === Background hierarchy (container-aware) === */
20
+ --tsu-bg-page: var(--tsu-neutral-1); /* app page background */
21
+ --tsu-bg-panel: var(--tsu-neutral-2); /* cards, sheets, popovers, dialogs */
22
+ --tsu-bg-control: var(--tsu-neutral-3); /* form controls (inputs, buttons) */
23
+ --tsu-bg-control-hover: var(--tsu-neutral-4);
24
+ --tsu-bg-overlay: rgba(0, 0, 0, 0.5); /* modal scrim */
25
+ --tsu-bg-overlay-soft: rgba(0, 0, 0, 0.35); /* sheet / bottom-scrim */
26
+
27
+ /* Back-compat aliases (existing components) */
28
+ --tsu-bg: var(--tsu-bg-page);
29
+ --tsu-surface: var(--tsu-bg-panel);
30
+ --tsu-surface-raised: var(--tsu-bg-control);
31
+ --tsu-surface-raised-hover: var(--tsu-bg-control-hover);
32
+
33
+ /* === Text hierarchy === */
34
+ --tsu-fg: var(--tsu-neutral-12);
35
+ --tsu-text-primary: var(--tsu-neutral-12);
36
+ --tsu-text-secondary: var(--tsu-neutral-11);
37
+ --tsu-text-subtle: var(--tsu-neutral-9);
38
+
39
+ /* === Border hierarchy === */
40
+ --tsu-border: var(--tsu-neutral-6);
41
+ --tsu-border-strong: var(--tsu-neutral-7);
42
+
43
+ /* === Focus state === */
44
+ --tsu-focus-bg: var(--tsu-bg-control-hover);
45
+ --tsu-focus-border: var(--tsu-accent-8);
46
+ --tsu-focus-ring: var(--tsu-accent-alpha-8);
47
+
48
+ /* === Pressed / active state (touch) — unified === */
49
+ --tsu-pressed: var(--tsu-neutral-alpha-3);
50
+ --tsu-pressed-hover: var(--tsu-neutral-alpha-4);
51
+ --tsu-pressed-accent: var(--tsu-accent-alpha-3);
52
+ --tsu-pressed-accent-hover: var(--tsu-accent-alpha-4);
53
+ /* back-compat alias */
54
+ --tsu-color-pressed: var(--tsu-pressed);
55
+ --tsu-color-pressed-accent: var(--tsu-pressed-accent);
56
+
57
+ /* === Disabled state — uniform 50% opacity, no grayscale ===
58
+ Applied on top of any variant. The whole component fades uniformly so
59
+ the neutral intent (gray border) and the accent intent stay visually
60
+ distinct even when disabled. */
61
+ --tsu-disabled-opacity: 0.5;
62
+ /* fallback colors for surfaces that cannot use opacity */
63
+ --tsu-disabled-bg: var(--tsu-bg-control);
64
+ --tsu-disabled-fg: var(--tsu-neutral-9);
65
+ --tsu-disabled-border: var(--tsu-neutral-7);
66
+ }
67
+
68
+ /* ==========================================================================
69
+ Palette variants
70
+ solid → filled control (button primary, checked toggle, switch track)
71
+ surface → bordered panel-tinted control (surface buttons, surface inputs)
72
+ subtle → tinted control, no border (segmented, chips, selected rows)
73
+ outline → transparent control with a border (outline buttons, inputs)
74
+ plain → text-like control (link buttons, menu items)
75
+ ========================================================================== */
76
+
77
+ :root {
78
+ /* ---- Neutral (olive) ---- */
79
+ --tsu-neutral-solid-bg: var(--tsu-neutral-9);
80
+ --tsu-neutral-solid-bg-hover: var(--tsu-neutral-10);
81
+ --tsu-neutral-solid-fg: var(--tsu-bg-page);
82
+
83
+ --tsu-neutral-surface-bg: var(--tsu-bg-panel);
84
+ --tsu-neutral-surface-bg-hover: var(--tsu-bg-control);
85
+ --tsu-neutral-surface-bg-active: var(--tsu-bg-control-hover);
86
+ --tsu-neutral-surface-border: var(--tsu-neutral-6);
87
+ --tsu-neutral-surface-border-hover: var(--tsu-neutral-7);
88
+ --tsu-neutral-surface-fg: var(--tsu-text-primary);
89
+
90
+ --tsu-neutral-subtle-bg: var(--tsu-neutral-alpha-3);
91
+ --tsu-neutral-subtle-bg-hover: var(--tsu-neutral-alpha-4);
92
+ --tsu-neutral-subtle-bg-active: var(--tsu-neutral-alpha-5);
93
+ --tsu-neutral-subtle-fg: var(--tsu-text-primary);
94
+
95
+ --tsu-neutral-outline-border: var(--tsu-neutral-6);
96
+ --tsu-neutral-outline-border-hover: var(--tsu-neutral-7);
97
+ --tsu-neutral-outline-bg-hover: var(--tsu-neutral-alpha-2);
98
+ --tsu-neutral-outline-bg-active: var(--tsu-neutral-alpha-3);
99
+ --tsu-neutral-outline-fg: var(--tsu-text-primary);
100
+
101
+ --tsu-neutral-plain-fg: var(--tsu-text-primary);
102
+ --tsu-neutral-plain-bg-hover: var(--tsu-neutral-alpha-3);
103
+ --tsu-neutral-plain-bg-active: var(--tsu-neutral-alpha-4);
104
+
105
+ /* ---- Accent (indigo) ---- */
106
+ --tsu-accent-solid-bg: var(--tsu-accent-9);
107
+ --tsu-accent-solid-bg-hover: var(--tsu-accent-10);
108
+ --tsu-accent-solid-fg: #ffffff;
109
+
110
+ --tsu-accent-surface-bg: var(--tsu-accent-alpha-2);
111
+ --tsu-accent-surface-bg-hover: var(--tsu-accent-alpha-3);
112
+ --tsu-accent-surface-bg-active: var(--tsu-accent-alpha-4);
113
+ --tsu-accent-surface-border: var(--tsu-accent-alpha-6);
114
+ --tsu-accent-surface-border-hover: var(--tsu-accent-alpha-7);
115
+ --tsu-accent-surface-fg: var(--tsu-accent-11);
116
+
117
+ --tsu-accent-subtle-bg: var(--tsu-accent-alpha-3);
118
+ --tsu-accent-subtle-bg-hover: var(--tsu-accent-alpha-4);
119
+ --tsu-accent-subtle-bg-active: var(--tsu-accent-alpha-5);
120
+ --tsu-accent-subtle-fg: var(--tsu-accent-11);
121
+
122
+ --tsu-accent-outline-border: var(--tsu-accent-alpha-7);
123
+ --tsu-accent-outline-border-hover: var(--tsu-accent-alpha-8);
124
+ --tsu-accent-outline-bg-hover: var(--tsu-accent-alpha-2);
125
+ --tsu-accent-outline-bg-active: var(--tsu-accent-alpha-3);
126
+ --tsu-accent-outline-fg: var(--tsu-accent-11);
127
+
128
+ --tsu-accent-plain-fg: var(--tsu-accent-11);
129
+ --tsu-accent-plain-bg-hover: var(--tsu-accent-alpha-3);
130
+ --tsu-accent-plain-bg-active: var(--tsu-accent-alpha-4);
131
+
132
+ /* ---- Danger (red) ---- */
133
+ --tsu-danger-solid-bg: var(--tsu-danger-9);
134
+ --tsu-danger-solid-bg-hover: var(--tsu-danger-10);
135
+ --tsu-danger-solid-fg: #ffffff;
136
+
137
+ --tsu-danger-surface-bg: var(--tsu-danger-alpha-2);
138
+ --tsu-danger-surface-bg-hover: var(--tsu-danger-alpha-3);
139
+ --tsu-danger-surface-bg-active: var(--tsu-danger-alpha-4);
140
+ --tsu-danger-surface-border: var(--tsu-danger-alpha-6);
141
+ --tsu-danger-surface-border-hover: var(--tsu-danger-alpha-7);
142
+ --tsu-danger-surface-fg: var(--tsu-danger-11);
143
+
144
+ --tsu-danger-subtle-bg: var(--tsu-danger-alpha-3);
145
+ --tsu-danger-subtle-bg-hover: var(--tsu-danger-alpha-4);
146
+ --tsu-danger-subtle-bg-active: var(--tsu-danger-alpha-5);
147
+ --tsu-danger-subtle-fg: var(--tsu-danger-11);
148
+
149
+ --tsu-danger-outline-border: var(--tsu-danger-alpha-7);
150
+ --tsu-danger-outline-border-hover: var(--tsu-danger-alpha-8);
151
+ --tsu-danger-outline-bg-hover: var(--tsu-danger-alpha-2);
152
+ --tsu-danger-outline-bg-active: var(--tsu-danger-alpha-3);
153
+ --tsu-danger-outline-fg: var(--tsu-danger-11);
154
+
155
+ --tsu-danger-plain-fg: var(--tsu-danger-11);
156
+ --tsu-danger-plain-bg-hover: var(--tsu-danger-alpha-3);
157
+ --tsu-danger-plain-bg-active: var(--tsu-danger-alpha-4);
158
+
159
+ /* ---- Success (green) ---- */
160
+ --tsu-success-solid-bg: var(--tsu-success-9);
161
+ --tsu-success-solid-bg-hover: var(--tsu-success-10);
162
+ --tsu-success-solid-fg: #ffffff;
163
+
164
+ --tsu-success-surface-bg: var(--tsu-success-alpha-2);
165
+ --tsu-success-surface-bg-hover: var(--tsu-success-alpha-3);
166
+ --tsu-success-surface-bg-active: var(--tsu-success-alpha-4);
167
+ --tsu-success-surface-border: var(--tsu-success-alpha-6);
168
+ --tsu-success-surface-border-hover: var(--tsu-success-alpha-7);
169
+ --tsu-success-surface-fg: var(--tsu-success-11);
170
+
171
+ --tsu-success-subtle-bg: var(--tsu-success-alpha-3);
172
+ --tsu-success-subtle-bg-hover: var(--tsu-success-alpha-4);
173
+ --tsu-success-subtle-bg-active: var(--tsu-success-alpha-5);
174
+ --tsu-success-subtle-fg: var(--tsu-success-11);
175
+
176
+ --tsu-success-outline-border: var(--tsu-success-alpha-7);
177
+ --tsu-success-outline-border-hover: var(--tsu-success-alpha-8);
178
+ --tsu-success-outline-bg-hover: var(--tsu-success-alpha-2);
179
+ --tsu-success-outline-bg-active: var(--tsu-success-alpha-3);
180
+ --tsu-success-outline-fg: var(--tsu-success-11);
181
+
182
+ --tsu-success-plain-fg: var(--tsu-success-11);
183
+ --tsu-success-plain-bg-hover: var(--tsu-success-alpha-3);
184
+ --tsu-success-plain-bg-active: var(--tsu-success-alpha-4);
185
+
186
+ /* ---- Warning (amber) ---- */
187
+ --tsu-warning-solid-bg: var(--tsu-warning-9);
188
+ --tsu-warning-solid-bg-hover: var(--tsu-warning-10);
189
+ --tsu-warning-solid-fg: #ffffff;
190
+
191
+ --tsu-warning-surface-bg: var(--tsu-warning-alpha-2);
192
+ --tsu-warning-surface-bg-hover: var(--tsu-warning-alpha-3);
193
+ --tsu-warning-surface-bg-active: var(--tsu-warning-alpha-4);
194
+ --tsu-warning-surface-border: var(--tsu-warning-alpha-6);
195
+ --tsu-warning-surface-border-hover: var(--tsu-warning-alpha-7);
196
+ --tsu-warning-surface-fg: var(--tsu-warning-11);
197
+
198
+ --tsu-warning-subtle-bg: var(--tsu-warning-alpha-3);
199
+ --tsu-warning-subtle-bg-hover: var(--tsu-warning-alpha-4);
200
+ --tsu-warning-subtle-bg-active: var(--tsu-warning-alpha-5);
201
+ --tsu-warning-subtle-fg: var(--tsu-warning-11);
202
+
203
+ --tsu-warning-outline-border: var(--tsu-warning-alpha-7);
204
+ --tsu-warning-outline-border-hover: var(--tsu-warning-alpha-8);
205
+ --tsu-warning-outline-bg-hover: var(--tsu-warning-alpha-2);
206
+ --tsu-warning-outline-bg-active: var(--tsu-warning-alpha-3);
207
+ --tsu-warning-outline-fg: var(--tsu-warning-11);
208
+
209
+ --tsu-warning-plain-fg: var(--tsu-warning-11);
210
+ --tsu-warning-plain-bg-hover: var(--tsu-warning-alpha-3);
211
+ --tsu-warning-plain-bg-active: var(--tsu-warning-alpha-4);
212
+ }
@@ -5,4 +5,5 @@
5
5
  0 4px 16px var(--tsu-neutral-alpha-8);
6
6
  --tsu-shadow-lg: 0 2px 6px var(--tsu-neutral-alpha-6),
7
7
  0 12px 32px var(--tsu-neutral-alpha-10);
8
+ --tsu-shadow-up: 0 -8px 32px var(--tsu-neutral-alpha-10);
8
9
  }
@@ -22,6 +22,20 @@
22
22
  --tsu-accent-11: var(--indigo-11);
23
23
  --tsu-accent-12: var(--indigo-12);
24
24
 
25
+ /* Accent alpha (indigo) — transparent tints for hover/active/overlays */
26
+ --tsu-accent-alpha-1: var(--indigo-a1);
27
+ --tsu-accent-alpha-2: var(--indigo-a2);
28
+ --tsu-accent-alpha-3: var(--indigo-a3);
29
+ --tsu-accent-alpha-4: var(--indigo-a4);
30
+ --tsu-accent-alpha-5: var(--indigo-a5);
31
+ --tsu-accent-alpha-6: var(--indigo-a6);
32
+ --tsu-accent-alpha-7: var(--indigo-a7);
33
+ --tsu-accent-alpha-8: var(--indigo-a8);
34
+ --tsu-accent-alpha-9: var(--indigo-a9);
35
+ --tsu-accent-alpha-10: var(--indigo-a10);
36
+ --tsu-accent-alpha-11: var(--indigo-a11);
37
+ --tsu-accent-alpha-12: var(--indigo-a12);
38
+
25
39
  /* Neutral (olive) */
26
40
  --tsu-neutral-1: var(--olive-1);
27
41
  --tsu-neutral-2: var(--olive-2);
@@ -1,4 +1,5 @@
1
1
  @import './color.css';
2
+ @import './semantic.css';
2
3
  @import './radius.css';
3
4
  @import './sizing.css';
4
5
  @import './typography.css';