@gtivr4/a1-design-system-react 0.1.0 → 0.2.3

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 (108) hide show
  1. package/package.json +1 -1
  2. package/src/color-scheme.css +586 -24
  3. package/src/components/accordion/Accordion.jsx +80 -0
  4. package/src/components/accordion/accordion.css +118 -0
  5. package/src/components/banner/Banner.jsx +66 -0
  6. package/src/components/banner/banner.css +205 -0
  7. package/src/components/bleed/Bleed.jsx +27 -0
  8. package/src/components/bleed/bleed.css +5 -0
  9. package/src/components/blockquote/Blockquote.jsx +40 -0
  10. package/src/components/blockquote/blockquote.css +166 -0
  11. package/src/components/breadcrumb/Breadcrumb.jsx +82 -0
  12. package/src/components/breadcrumb/breadcrumb.css +133 -0
  13. package/src/components/button/button.css +42 -12
  14. package/src/components/button-container/ButtonContainer.jsx +20 -1
  15. package/src/components/button-container/button-container.css +19 -1
  16. package/src/components/calendar/Calendar.jsx +383 -0
  17. package/src/components/calendar/calendar.css +225 -0
  18. package/src/components/card/Card.jsx +50 -12
  19. package/src/components/card/card.css +178 -14
  20. package/src/components/checkbox-group/CheckboxGroup.jsx +120 -0
  21. package/src/components/checkbox-group/checkbox-group.css +304 -0
  22. package/src/components/cluster/Cluster.jsx +52 -0
  23. package/src/components/cluster/cluster.css +9 -0
  24. package/src/components/code/Code.jsx +135 -0
  25. package/src/components/code/code.css +60 -0
  26. package/src/components/data-table/DataTable.jsx +721 -0
  27. package/src/components/data-table/DataTableFilters.jsx +339 -0
  28. package/src/components/data-table/data-table-filters.css +259 -0
  29. package/src/components/data-table/data-table.css +425 -0
  30. package/src/components/dialog/Dialog.jsx +45 -2
  31. package/src/components/dialog/dialog.css +13 -4
  32. package/src/components/divider/Divider.jsx +64 -0
  33. package/src/components/divider/divider.css +170 -0
  34. package/src/components/field/CreditCardField.jsx +131 -0
  35. package/src/components/field/DateField.jsx +11 -0
  36. package/src/components/field/NumberField.jsx +11 -0
  37. package/src/components/field/PhoneField.jsx +107 -0
  38. package/src/components/field/SelectField.jsx +86 -0
  39. package/src/components/field/TextField.jsx +83 -0
  40. package/src/components/field/TextareaField.jsx +147 -0
  41. package/src/components/field/TimeField.jsx +11 -0
  42. package/src/components/field/ZipField.jsx +114 -0
  43. package/src/components/field/credit-card.css +30 -0
  44. package/src/components/field/field.css +380 -0
  45. package/src/components/field/textarea-field.css +185 -0
  46. package/src/components/field-row/FieldRow.jsx +23 -0
  47. package/src/components/field-row/field-row.css +51 -0
  48. package/src/components/fieldset/Fieldset.jsx +49 -0
  49. package/src/components/fieldset/fieldset.css +75 -0
  50. package/src/components/figure/Figure.jsx +63 -0
  51. package/src/components/figure/figure.css +97 -0
  52. package/src/components/grid/Grid.jsx +36 -2
  53. package/src/components/grid/grid.css +129 -4
  54. package/src/components/heading/Heading.jsx +41 -1
  55. package/src/components/heading/heading.css +65 -4
  56. package/src/components/icon/icon.css +1 -0
  57. package/src/components/icon-button/icon-button.css +1 -0
  58. package/src/components/inline/inline.css +51 -0
  59. package/src/components/inline-editable/InlineEditable.jsx +77 -0
  60. package/src/components/inline-editable/inline-editable.css +47 -0
  61. package/src/components/inset/Inset.jsx +27 -0
  62. package/src/components/inset/inset.css +6 -0
  63. package/src/components/labels/Labels.jsx +5 -5
  64. package/src/components/link/Link.jsx +2 -3
  65. package/src/components/link/link.css +30 -1
  66. package/src/components/list/List.jsx +92 -0
  67. package/src/components/list/list.css +178 -0
  68. package/src/components/menu/Menu.jsx +243 -10
  69. package/src/components/menu/menu.css +157 -17
  70. package/src/components/message/Message.jsx +25 -50
  71. package/src/components/message/message.css +50 -33
  72. package/src/components/notification/Notification.jsx +1 -1
  73. package/src/components/page-layout/PageLayout.jsx +16 -1
  74. package/src/components/page-layout/page-layout.css +97 -4
  75. package/src/components/page-nav/PageNav.jsx +110 -0
  76. package/src/components/page-nav/page-nav.css +167 -0
  77. package/src/components/paragraph/Paragraph.jsx +35 -2
  78. package/src/components/paragraph/paragraph.css +38 -1
  79. package/src/components/radio-group/RadioGroup.jsx +121 -0
  80. package/src/components/radio-group/radio-group.css +268 -0
  81. package/src/components/section/Section.jsx +108 -0
  82. package/src/components/section/section.css +280 -0
  83. package/src/components/segmented-control/SegmentedControl.jsx +4 -0
  84. package/src/components/segmented-control/segmented.css +13 -0
  85. package/src/components/side-nav/SideNav.jsx +29 -9
  86. package/src/components/side-nav/scrim.css +1 -1
  87. package/src/components/side-nav/side-nav.css +70 -32
  88. package/src/components/snackbar/Snackbar.jsx +56 -0
  89. package/src/components/snackbar/snackbar.css +113 -0
  90. package/src/components/spacer/Spacer.jsx +36 -0
  91. package/src/components/spacer/spacer.css +44 -0
  92. package/src/components/stack/Stack.jsx +100 -0
  93. package/src/components/stack/stack.css +37 -0
  94. package/src/components/switch/Switch.jsx +114 -0
  95. package/src/components/switch/switch.css +276 -0
  96. package/src/components/system-banner/SystemBanner.jsx +57 -0
  97. package/src/components/system-banner/system-banner.css +118 -0
  98. package/src/components/tabs/Tabs.jsx +96 -28
  99. package/src/components/tabs/tabs.css +352 -15
  100. package/src/components/token-select/TokenSelect.jsx +159 -0
  101. package/src/components/token-select/token-select.css +110 -0
  102. package/src/components/top-header/TopHeader.jsx +641 -0
  103. package/src/components/top-header/top-header.css +337 -0
  104. package/src/illustrations/ComponentThumbnails.jsx +227 -0
  105. package/src/index.js +41 -5
  106. package/src/themes.css +256 -5
  107. package/src/utilities/spacing.css +8 -0
  108. package/src/utilities/sr-only.css +16 -0
@@ -1,10 +1,58 @@
1
1
  /* ─── Tabs ────────────────────────────────────────────────────────────────── */
2
2
 
3
3
  .a1-tabs {
4
+ box-sizing: border-box;
4
5
  display: flex;
5
6
  flex-direction: column;
6
7
  }
7
8
 
9
+ /* ─── Tab list wrapper ────────────────────────────────────────────────────── */
10
+
11
+ /*
12
+ * Wrapper is a flex row — scroll buttons are flex siblings of the tab list.
13
+ * Line variant: border-bottom lives on wrapper (always full-width).
14
+ * Folder variant: border-bottom, padding, and align-items live on wrapper.
15
+ */
16
+
17
+ .a1-tab-list-wrapper {
18
+ display: flex;
19
+ align-items: stretch;
20
+ }
21
+
22
+ .a1-tab-list-wrapper--line {
23
+ border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-subtle);
24
+ }
25
+
26
+ .a1-tab-list-wrapper--folder {
27
+ align-items: flex-end;
28
+ gap: var(--base-spacing-4);
29
+ padding: var(--base-spacing-8) var(--base-spacing-8) 0;
30
+ border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
31
+ }
32
+
33
+ /* ─── Scroll buttons (line variant only) ─────────────────────────────────── */
34
+
35
+ .a1-tab-list__scroll-btn {
36
+ flex-shrink: 0;
37
+ display: inline-flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ width: var(--component-tab-scroll-button-width);
41
+ background: var(--semantic-color-surface-page);
42
+ border: none;
43
+ cursor: pointer;
44
+ color: var(--semantic-color-text-muted);
45
+ padding: 0;
46
+ }
47
+
48
+ .a1-tab-list__scroll-btn:hover {
49
+ color: var(--semantic-color-text-default);
50
+ }
51
+
52
+ .a1-tab-list__scroll-btn .a1-icon {
53
+ font-size: var(--component-tab-scroll-button-icon-size);
54
+ }
55
+
8
56
  /* ─── Tab list ────────────────────────────────────────────────────────────── */
9
57
 
10
58
  .a1-tab-list {
@@ -12,10 +60,20 @@
12
60
  flex-shrink: 0;
13
61
  }
14
62
 
63
+ /* Line variant gets overflow scroll; the tab list scrolls, wrapper border stays full-width */
64
+ .a1-tab-list--scrollable {
65
+ flex: 1;
66
+ overflow-x: auto;
67
+ scrollbar-width: none;
68
+ }
69
+
70
+ .a1-tab-list--scrollable::-webkit-scrollbar {
71
+ display: none;
72
+ }
73
+
15
74
  /* ── Line variant ───────────────────────────────────────────────────────── */
16
75
 
17
76
  .a1-tab-list--line {
18
- border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-subtle);
19
77
  gap: 0;
20
78
  }
21
79
 
@@ -24,8 +82,7 @@
24
82
  .a1-tab-list--folder {
25
83
  align-items: flex-end;
26
84
  gap: var(--base-spacing-4);
27
- padding: var(--base-spacing-8) var(--base-spacing-8) 0;
28
- border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
85
+ flex: 1;
29
86
  }
30
87
 
31
88
  /* ─── Tab button ──────────────────────────────────────────────────────────── */
@@ -48,12 +105,53 @@
48
105
  border-radius: var(--base-radius-control);
49
106
  }
50
107
 
108
+ /* ── Icon styles ─────────────────────────────────────────────────────────── */
109
+
110
+ .a1-tab__icon {
111
+ font-size: var(--component-tab-icon-size);
112
+ flex-shrink: 0;
113
+ }
114
+
115
+ .a1-tab--icon-above {
116
+ flex-direction: column;
117
+ align-items: center;
118
+ gap: var(--base-spacing-4);
119
+ white-space: normal;
120
+ text-align: center;
121
+ }
122
+
123
+ .a1-tab--icon-above .a1-tab__icon {
124
+ font-size: var(--component-tab-icon-above-size);
125
+ }
126
+
127
+ /* ── Count badge ─────────────────────────────────────────────────────────── */
128
+
129
+ .a1-tab__count {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ min-width: var(--component-tab-count-min-width);
134
+ height: var(--component-tab-count-height);
135
+ padding: 0 var(--component-tab-count-padding-inline);
136
+ border-radius: var(--component-tab-count-border-radius);
137
+ font-size: var(--component-tab-count-font-size);
138
+ font-weight: var(--base-font-weight-medium);
139
+ line-height: var(--component-tab-count-line-height);
140
+ background: var(--semantic-color-surface-raised);
141
+ color: var(--semantic-color-text-muted);
142
+ }
143
+
144
+ .a1-tab[aria-selected="true"] .a1-tab__count {
145
+ background: color-mix(in srgb, var(--semantic-color-action-background) 12%, transparent);
146
+ color: var(--semantic-color-action-background);
147
+ }
148
+
51
149
  /* ── Line tab ───────────────────────────────────────────────────────────── */
52
150
 
53
151
  .a1-tab--line {
54
152
  padding: var(--component-tab-padding-block) var(--component-tab-padding-inline);
55
153
  font-size: var(--semantic-font-size-body-sm);
56
- font-weight: var(--component-tab-font-weight-default);
154
+ font-weight: var(--base-font-weight-medium);
57
155
  color: var(--semantic-color-text-muted);
58
156
  border-bottom: var(--component-tab-indicator-size) solid transparent;
59
157
  margin-bottom: var(--component-tab-margin-bottom);
@@ -66,8 +164,8 @@
66
164
 
67
165
  .a1-tab--line[aria-selected="true"] {
68
166
  color: var(--semantic-color-action-background);
69
- font-weight: var(--component-tab-font-weight-active);
70
167
  border-bottom-color: var(--semantic-color-action-background);
168
+ cursor: default;
71
169
  }
72
170
 
73
171
  /* ── Folder tab ─────────────────────────────────────────────────────────── */
@@ -75,14 +173,15 @@
75
173
  .a1-tab--folder {
76
174
  padding: var(--component-tab-padding-block) var(--component-tab-padding-inline);
77
175
  font-size: var(--semantic-font-size-body-sm);
78
- font-weight: var(--component-tab-font-weight-default);
176
+ font-weight: var(--base-font-weight-medium);
79
177
  color: var(--semantic-color-text-muted);
80
178
  background: var(--semantic-color-surface-panel);
81
179
  border: var(--component-tab-border-width) solid var(--semantic-color-border-default);
82
- border-bottom: none;
180
+ border-bottom: var(--component-tab-border-width) solid transparent;
83
181
  border-radius: var(--base-radius-lg) var(--base-radius-lg) 0 0;
84
182
  margin-bottom: var(--component-tab-margin-bottom);
85
183
  position: relative;
184
+ overflow: visible;
86
185
  }
87
186
 
88
187
  .a1-tab--folder:hover:not([aria-selected="true"]) {
@@ -93,9 +192,184 @@
93
192
  .a1-tab--folder[aria-selected="true"] {
94
193
  background: var(--semantic-color-surface-page);
95
194
  color: var(--semantic-color-text-default);
96
- font-weight: var(--component-tab-font-weight-active);
97
195
  border-color: var(--semantic-color-border-strong);
196
+ border-bottom-color: var(--semantic-color-surface-page);
98
197
  z-index: var(--component-tab-z-index-active);
198
+ cursor: default;
199
+ }
200
+
201
+ /* Outward curves at active folder tab bottom corners */
202
+ .a1-tab--folder[aria-selected="true"]::before,
203
+ .a1-tab--folder[aria-selected="true"]::after {
204
+ content: "";
205
+ position: absolute;
206
+ bottom: var(--component-tab-margin-bottom);
207
+ width: var(--base-radius-lg);
208
+ height: var(--base-radius-lg);
209
+ pointer-events: none;
210
+ z-index: var(--component-tab-z-index-active);
211
+ background: var(--semantic-color-surface-page);
212
+ }
213
+
214
+ .a1-tab--folder[aria-selected="true"]::before {
215
+ left: calc(-1 * var(--base-radius-lg));
216
+ border-right: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
217
+ border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
218
+ border-bottom-right-radius: var(--base-radius-lg);
219
+ }
220
+
221
+ .a1-tab--folder[aria-selected="true"]::after {
222
+ right: calc(-1 * var(--base-radius-lg));
223
+ border-left: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
224
+ border-bottom: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
225
+ border-bottom-left-radius: var(--base-radius-lg);
226
+ }
227
+
228
+ /* ── Progress variant ───────────────────────────────────────────────────── */
229
+
230
+ .a1-tab-list-wrapper--progress {
231
+ border-bottom: none;
232
+ }
233
+
234
+ .a1-tab-list--progress {
235
+ counter-reset: progress-step;
236
+ gap: 0;
237
+ align-items: flex-start;
238
+ flex: 1;
239
+ }
240
+
241
+ /* Custom properties for responsive step geometry */
242
+ .a1-tab--progress {
243
+ --_step-pad-top: var(--base-spacing-8);
244
+ --_step-size: var(--component-tab-progress-step-size);
245
+ }
246
+
247
+ @media (--bp-md-up) {
248
+ .a1-tab--progress {
249
+ --_step-pad-top: var(--base-spacing-16);
250
+ --_step-size: var(--component-tab-progress-step-size-md);
251
+ }
252
+ }
253
+
254
+ .a1-tab--progress {
255
+ counter-increment: progress-step;
256
+ flex: 1;
257
+ flex-direction: column;
258
+ align-items: center;
259
+ gap: var(--base-spacing-6);
260
+ padding: var(--_step-pad-top) var(--base-spacing-4) var(--base-spacing-8);
261
+ font-size: var(--semantic-font-size-body-sm);
262
+ font-weight: var(--base-font-weight-medium);
263
+ color: var(--semantic-color-text-muted);
264
+ background: transparent;
265
+ border: none;
266
+ position: relative;
267
+ white-space: normal;
268
+ text-align: center;
269
+ cursor: pointer;
270
+ }
271
+
272
+ .a1-tab--progress:hover {
273
+ color: var(--semantic-color-text-default);
274
+ }
275
+
276
+ /* Connecting line — left half (coming from previous step) */
277
+ .a1-tab--progress:not(:first-child)::before {
278
+ content: "";
279
+ position: absolute;
280
+ top: calc(var(--_step-pad-top) + var(--_step-size) / 2);
281
+ left: 0;
282
+ width: 50%;
283
+ height: var(--component-tab-progress-line-height);
284
+ background: var(--semantic-color-border-subtle);
285
+ z-index: 0;
286
+ }
287
+
288
+ /* Connecting line — right half (going to next step) */
289
+ .a1-tab--progress:not(:last-child)::after {
290
+ content: "";
291
+ position: absolute;
292
+ top: calc(var(--_step-pad-top) + var(--_step-size) / 2);
293
+ right: 0;
294
+ width: 50%;
295
+ height: var(--component-tab-progress-line-height);
296
+ background: var(--semantic-color-border-subtle);
297
+ z-index: 0;
298
+ }
299
+
300
+ /* Color the line going INTO a completed or in-progress step */
301
+ .a1-tab--progress.a1-tab--status-completed:not(:first-child)::before,
302
+ .a1-tab--progress.a1-tab--status-in-progress:not(:first-child)::before {
303
+ background: var(--semantic-color-status-success-background);
304
+ }
305
+
306
+ /* Color the line going OUT of a completed step */
307
+ .a1-tab--progress.a1-tab--status-completed:not(:last-child)::after {
308
+ background: var(--semantic-color-status-success-background);
309
+ }
310
+
311
+ /* Step circle */
312
+ .a1-tab__step {
313
+ width: var(--_step-size);
314
+ height: var(--_step-size);
315
+ border-radius: 50%;
316
+ display: inline-flex;
317
+ align-items: center;
318
+ justify-content: center;
319
+ font-size: var(--component-tab-progress-step-font-size);
320
+ font-weight: var(--base-font-weight-semibold);
321
+ position: relative;
322
+ z-index: 1;
323
+ background: var(--semantic-color-surface-page);
324
+ border: var(--component-tab-progress-step-border-width) solid var(--semantic-color-border-default);
325
+ color: var(--semantic-color-text-muted);
326
+ transition:
327
+ background var(--semantic-motion-duration-fast),
328
+ border-color var(--semantic-motion-duration-fast),
329
+ box-shadow var(--semantic-motion-duration-fast);
330
+ }
331
+
332
+ /* Step number via CSS counter (hidden when completed — icon replaces it) */
333
+ .a1-tab__step::before {
334
+ content: counter(progress-step);
335
+ }
336
+
337
+ /* in-progress state */
338
+ .a1-tab--status-in-progress .a1-tab__step {
339
+ background: var(--semantic-color-action-background);
340
+ border-color: var(--semantic-color-action-background);
341
+ color: var(--semantic-color-surface-page);
342
+ }
343
+
344
+ /* completed state */
345
+ .a1-tab--status-completed .a1-tab__step {
346
+ background: var(--semantic-color-status-success-background);
347
+ border-color: var(--semantic-color-status-success-background);
348
+ color: var(--semantic-color-status-success-foreground);
349
+ }
350
+
351
+ .a1-tab--status-completed .a1-tab__step::before {
352
+ content: none;
353
+ }
354
+
355
+ .a1-tab--status-completed .a1-tab__step .a1-icon {
356
+ font-size: var(--component-tab-progress-completed-icon-size);
357
+ }
358
+
359
+ /* Selected (active view) — ring around step circle */
360
+ .a1-tab--progress[aria-selected="true"] {
361
+ color: var(--semantic-color-text-default);
362
+ cursor: default;
363
+ }
364
+
365
+ .a1-tab--progress[aria-selected="true"] .a1-tab__step {
366
+ box-shadow: var(--component-tab-progress-selected-ring);
367
+ }
368
+
369
+ /* Selected todo step gets accent border + color */
370
+ .a1-tab--progress[aria-selected="true"]:not(.a1-tab--status-completed):not(.a1-tab--status-in-progress) .a1-tab__step {
371
+ border-color: var(--semantic-color-action-background);
372
+ color: var(--semantic-color-action-background);
99
373
  }
100
374
 
101
375
  /* ─── Level 2 ─────────────────────────────────────────────────────────────── */
@@ -111,10 +385,73 @@
111
385
  border-radius: var(--base-radius-control) var(--base-radius-control) 0 0;
112
386
  }
113
387
 
114
- .a1-tabs--level-2 .a1-tab-list--folder {
388
+ .a1-tabs--level-2 .a1-tab-list-wrapper--folder {
115
389
  padding: var(--base-spacing-4) var(--base-spacing-4) 0;
116
390
  }
117
391
 
392
+ /* Level-2 folder curves (smaller radius) */
393
+ .a1-tabs--level-2 .a1-tab--folder[aria-selected="true"]::before {
394
+ left: calc(-1 * var(--base-radius-control));
395
+ width: var(--base-radius-control);
396
+ height: var(--base-radius-control);
397
+ border-bottom-right-radius: var(--base-radius-control);
398
+ }
399
+
400
+ .a1-tabs--level-2 .a1-tab--folder[aria-selected="true"]::after {
401
+ right: calc(-1 * var(--base-radius-control));
402
+ width: var(--base-radius-control);
403
+ height: var(--base-radius-control);
404
+ border-bottom-left-radius: var(--base-radius-control);
405
+ }
406
+
407
+ /* ─── Responsive tab labels (xs/sm: icon-only for inactive tabs) ─────────── */
408
+
409
+ @media (--bp-sm-down) {
410
+ .a1-tab {
411
+ position: relative;
412
+ }
413
+
414
+ /* Visually hide label on inactive tabs — kept in DOM for accessibility */
415
+ .a1-tab[aria-selected="false"] .a1-tab__label {
416
+ position: absolute;
417
+ width: 1px;
418
+ height: 1px;
419
+ padding: 0;
420
+ margin: -1px;
421
+ overflow: hidden;
422
+ clip: rect(0, 0, 0, 0);
423
+ white-space: nowrap;
424
+ }
425
+ }
426
+
427
+ /* ─── Responsive font sizes ───────────────────────────────────────────────── */
428
+
429
+ @media (--bp-md-up) {
430
+ .a1-tab--line,
431
+ .a1-tab--folder,
432
+ .a1-tab--progress {
433
+ font-size: var(--semantic-font-size-body-md);
434
+ }
435
+
436
+ .a1-tabs--level-2 .a1-tab--line,
437
+ .a1-tabs--level-2 .a1-tab--folder {
438
+ font-size: var(--semantic-font-size-body-sm);
439
+ }
440
+ }
441
+
442
+ @media (--bp-lg-up) {
443
+ .a1-tab--line,
444
+ .a1-tab--folder,
445
+ .a1-tab--progress {
446
+ font-size: var(--semantic-font-size-body-lg);
447
+ }
448
+
449
+ .a1-tabs--level-2 .a1-tab--line,
450
+ .a1-tabs--level-2 .a1-tab--folder {
451
+ font-size: var(--semantic-font-size-body-md);
452
+ }
453
+ }
454
+
118
455
  /* ─── Tab panel ───────────────────────────────────────────────────────────── */
119
456
 
120
457
  .a1-tab-panel--line {
@@ -123,13 +460,13 @@
123
460
 
124
461
  .a1-tab-panel--folder {
125
462
  background: var(--semantic-color-surface-page);
126
- border: var(--component-tab-border-width) solid var(--semantic-color-border-strong);
127
- border-top: none;
128
- border-radius: 0 0 var(--base-radius-lg) var(--base-radius-lg);
129
- padding: var(--base-spacing-16);
463
+ padding: var(--base-spacing-16) 0 0;
130
464
  }
131
465
 
132
466
  .a1-tabs--level-2 .a1-tab-panel--folder {
133
- border-radius: 0 0 var(--base-radius-control) var(--base-radius-control);
134
- padding: var(--base-spacing-12);
467
+ padding: var(--base-spacing-12) 0 0;
468
+ }
469
+
470
+ .a1-tab-panel--progress {
471
+ padding: var(--base-spacing-24) 0;
135
472
  }
@@ -0,0 +1,159 @@
1
+ import { useId, useRef, useState } from "react";
2
+ import { Menu, MenuSection } from "../menu/Menu.jsx";
3
+ import { Icon } from "../icon/Icon.jsx";
4
+ import "./token-select.css";
5
+
6
+ // Parse "var(--base-color-accent-500)" → { ramp: "accent", stop: 500 }
7
+ function parseRef(value) {
8
+ const m = value?.match(/^var\(--base-color-([a-z]+)-(\d+)\)$/);
9
+ return m ? { ramp: m[1], stop: parseInt(m[2]) } : null;
10
+ }
11
+
12
+ // Identify which ramp token a raw hex value corresponds to
13
+ function identifyToken(hex, rampColors) {
14
+ const n = hex?.toLowerCase();
15
+ if (!n?.startsWith("#")) return null;
16
+ for (const [ramp, stops] of Object.entries(rampColors)) {
17
+ for (const [stop, color] of Object.entries(stops)) {
18
+ if (color?.toLowerCase() === n) return { ramp, stop: parseInt(stop) };
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+
24
+ function TokenItem({ ramp, stop, hex, selected, onSelect }) {
25
+ return (
26
+ <button
27
+ type="button"
28
+ className={`a1-token-item${selected ? " a1-token-item--selected" : ""}`}
29
+ onClick={() => onSelect(ramp, stop)}
30
+ >
31
+ <span className="a1-token-item__swatch" style={{ background: hex }} aria-hidden="true" />
32
+ <span className="a1-token-item__name">{ramp}-{stop}</span>
33
+ <span className="a1-token-item__hex">{hex}</span>
34
+ {selected && <Icon name="check" className="a1-token-item__check" />}
35
+ </button>
36
+ );
37
+ }
38
+
39
+ const SIZES = ["comfortable", "default", "compact"];
40
+
41
+ export function TokenSelect({
42
+ label,
43
+ value,
44
+ onChange,
45
+ rampColors = {},
46
+ rampLabels = {},
47
+ suggestedRamp,
48
+ size = "compact",
49
+ disabled = false,
50
+ id: providedId,
51
+ className = "",
52
+ }) {
53
+ const autoId = useId();
54
+ const id = providedId ?? autoId;
55
+ const triggerRef = useRef(null);
56
+ const [open, setOpen] = useState(false);
57
+
58
+ const rampNames = Object.keys(rampColors);
59
+ const primary = (suggestedRamp && rampColors[suggestedRamp]) ? suggestedRamp : (rampNames[0] ?? "");
60
+ const others = rampNames.filter((r) => r !== primary);
61
+
62
+ const stops = Object.keys(rampColors[primary] ?? rampColors[rampNames[0]] ?? {})
63
+ .map(Number)
64
+ .filter((n) => !isNaN(n))
65
+ .sort((a, b) => a - b);
66
+
67
+ const ref = parseRef(value);
68
+ const identified = ref ?? identifyToken(value, rampColors);
69
+ const resolvedHex = ref
70
+ ? (rampColors[ref.ramp]?.[ref.stop] ?? null)
71
+ : (/^#[0-9a-f]{3,8}$/i.test(value) ? value : null);
72
+
73
+ const varName = !identified && value?.match(/^var\(\s*(--[^,)\s]+)/)?.[1];
74
+ const displayLabel = identified
75
+ ? `${identified.ramp}-${identified.stop}`
76
+ : varName ?? "Choose…";
77
+
78
+ const resolvedSize = SIZES.includes(size) ? size : "compact";
79
+
80
+ function handleSelect(ramp, stop) {
81
+ onChange?.(`var(--base-color-${ramp}-${stop})`);
82
+ setOpen(false);
83
+ }
84
+
85
+ const classes = [
86
+ "a1-field",
87
+ `a1-field--${resolvedSize}`,
88
+ "a1-token-select",
89
+ disabled && "a1-field--disabled",
90
+ className,
91
+ ].filter(Boolean).join(" ");
92
+
93
+ return (
94
+ <div className={classes}>
95
+ {label && (
96
+ <label className="a1-field__label" htmlFor={id}>{label}</label>
97
+ )}
98
+ <div className="a1-field__control">
99
+ <button
100
+ ref={triggerRef}
101
+ id={id}
102
+ type="button"
103
+ className="a1-field__select a1-token-select__trigger"
104
+ disabled={disabled}
105
+ onClick={() => setOpen((v) => !v)}
106
+ aria-expanded={open}
107
+ aria-haspopup="listbox"
108
+ >
109
+ {resolvedHex && (
110
+ <span
111
+ className="a1-token-select__swatch"
112
+ style={{ background: resolvedHex }}
113
+ aria-hidden="true"
114
+ />
115
+ )}
116
+ <span className="a1-token-select__value">{displayLabel}</span>
117
+ </button>
118
+ <span className="a1-field__chevron" aria-hidden="true">
119
+ <Icon name="expand_more" />
120
+ </span>
121
+ </div>
122
+
123
+ <Menu
124
+ open={open}
125
+ onClose={() => setOpen(false)}
126
+ anchorRef={triggerRef}
127
+ aria-label={label ?? "Select a token"}
128
+ >
129
+ <MenuSection label={rampLabels[primary] ?? primary}>
130
+ {stops.map((stop) => (
131
+ <TokenItem
132
+ key={stop}
133
+ ramp={primary}
134
+ stop={stop}
135
+ hex={rampColors[primary]?.[stop] ?? "#ccc"}
136
+ selected={identified?.ramp === primary && identified?.stop === stop}
137
+ onSelect={handleSelect}
138
+ />
139
+ ))}
140
+ </MenuSection>
141
+
142
+ {others.map((ramp) => (
143
+ <MenuSection key={ramp} label={rampLabels[ramp] ?? ramp}>
144
+ {stops.map((stop) => (
145
+ <TokenItem
146
+ key={stop}
147
+ ramp={ramp}
148
+ stop={stop}
149
+ hex={rampColors[ramp]?.[stop] ?? "#ccc"}
150
+ selected={identified?.ramp === ramp && identified?.stop === stop}
151
+ onSelect={handleSelect}
152
+ />
153
+ ))}
154
+ </MenuSection>
155
+ ))}
156
+ </Menu>
157
+ </div>
158
+ );
159
+ }