@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
@@ -0,0 +1,339 @@
1
+ import { useRef, useState } from "react";
2
+ import { Button } from "../button/Button.jsx";
3
+ import { Icon } from "../icon/Icon.jsx";
4
+ import { Menu, MenuSection, MenuItem } from "../menu/Menu.jsx";
5
+ import "./data-table-filters.css";
6
+
7
+ /**
8
+ * filters: Array<{
9
+ * key: string,
10
+ * label: string,
11
+ * type?: "single" | "multi", default "single"
12
+ * options: Array<{ value: string, label: string }>,
13
+ * }>
14
+ * value: Record<string, string | string[]>
15
+ * single → string ("" = no filter)
16
+ * multi → string[] ([] = no filter)
17
+ *
18
+ * searchValue?: string
19
+ * onSearchChange?: (text: string) => void
20
+ * searchColumn?: string — key of column being searched; "" = all
21
+ * onSearchColumnChange?: (key: string) => void
22
+ * searchableColumns?: Array<{ key: string, label: string }>
23
+ */
24
+
25
+ // ── Helpers ──────────────────────────────────────────────────────────────────
26
+
27
+ function getSublabel(filter, selected) {
28
+ if (filter.type === "multi") {
29
+ const arr = Array.isArray(selected) ? selected : [];
30
+ if (arr.length === 0) return null;
31
+ if (arr.length === 1) return filter.options.find((o) => o.value === arr[0])?.label ?? arr[0];
32
+ return `${arr.length}`;
33
+ }
34
+ if (!selected) return null;
35
+ return filter.options.find((o) => o.value === selected)?.label ?? selected;
36
+ }
37
+
38
+ function isFilterActive(filter, selected) {
39
+ if (filter.type === "multi") return Array.isArray(selected) && selected.length > 0;
40
+ return Boolean(selected);
41
+ }
42
+
43
+ function radioIcon(checked) { return checked ? "radio_button_checked" : "radio_button_unchecked"; }
44
+ function checkIcon(checked) { return checked ? "check_box" : "check_box_outline_blank"; }
45
+
46
+ // ── FilterChip (desktop only) ─────────────────────────────────────────────────
47
+
48
+ function FilterChip({ filter, selected, onSet }) {
49
+ const [open, setOpen] = useState(false);
50
+ const anchorRef = useRef(null);
51
+
52
+ const isMulti = filter.type === "multi";
53
+ const arr = isMulti ? (Array.isArray(selected) ? selected : []) : null;
54
+ const sublabel = getSublabel(filter, selected);
55
+ const isActive = isFilterActive(filter, selected);
56
+
57
+ function handleOption(optValue) {
58
+ if (isMulti) {
59
+ const next = arr.includes(optValue)
60
+ ? arr.filter((v) => v !== optValue)
61
+ : [...arr, optValue];
62
+ onSet(next);
63
+ // keep open so user can pick multiple
64
+ } else {
65
+ onSet(selected === optValue ? "" : optValue);
66
+ setOpen(false); // close after single pick
67
+ }
68
+ }
69
+
70
+ return (
71
+ <div className="a1-dt-filters__chip-wrap">
72
+ <button
73
+ ref={anchorRef}
74
+ type="button"
75
+ className={[
76
+ "a1-dt-filters__chip",
77
+ isActive && "a1-dt-filters__chip--active",
78
+ ]
79
+ .filter(Boolean)
80
+ .join(" ")}
81
+ onClick={() => setOpen((v) => !v)}
82
+ aria-expanded={open}
83
+ aria-haspopup="listbox"
84
+ >
85
+ <span>{filter.label}</span>
86
+ {sublabel && (
87
+ <>
88
+ <span className="a1-dt-filters__chip-sep" aria-hidden="true">:</span>
89
+ <span className="a1-dt-filters__chip-value">{sublabel}</span>
90
+ </>
91
+ )}
92
+ <Icon
93
+ name={open ? "expand_less" : "expand_more"}
94
+ className="a1-dt-filters__chip-icon"
95
+ />
96
+ </button>
97
+
98
+ <Menu
99
+ open={open}
100
+ anchorRef={anchorRef}
101
+ onClose={() => setOpen(false)}
102
+ aria-label={filter.label}
103
+ >
104
+ {!isMulti && (
105
+ <MenuItem
106
+ key="__all__"
107
+ icon={radioIcon(!selected)}
108
+ className={!selected ? "a1-dt-filters__item--on" : ""}
109
+ onClick={() => { onSet(""); setOpen(false); }}
110
+ >
111
+ All
112
+ </MenuItem>
113
+ )}
114
+ {filter.options.map((opt) => {
115
+ const checked = isMulti ? arr.includes(opt.value) : selected === opt.value;
116
+ const icon = isMulti ? checkIcon(checked) : radioIcon(checked);
117
+ return (
118
+ <MenuItem
119
+ key={opt.value}
120
+ icon={icon}
121
+ className={checked ? "a1-dt-filters__item--on" : ""}
122
+ onClick={() => handleOption(opt.value)}
123
+ >
124
+ {opt.label}
125
+ </MenuItem>
126
+ );
127
+ })}
128
+ {isMulti && isActive && (
129
+ <div className="a1-dt-filters__menu-clear">
130
+ <Button
131
+ variant="tertiary"
132
+ size="sm"
133
+ icon="close"
134
+ onClick={() => { onSet([]); setOpen(false); }}
135
+ >
136
+ Clear
137
+ </Button>
138
+ </div>
139
+ )}
140
+ </Menu>
141
+ </div>
142
+ );
143
+ }
144
+
145
+ // ── DataTableFilters ──────────────────────────────────────────────────────────
146
+
147
+ export function DataTableFilters({
148
+ filters = [],
149
+ value = {},
150
+ onChange,
151
+ searchValue = "",
152
+ onSearchChange,
153
+ searchColumn = "",
154
+ onSearchColumnChange,
155
+ searchableColumns,
156
+ className = "",
157
+ }) {
158
+ const [mobileOpen, setMobileOpen] = useState(false);
159
+ const mobileTriggerRef = useRef(null);
160
+
161
+ const hasSearch = Boolean(onSearchChange);
162
+
163
+ const activeCount = filters.filter((f) => isFilterActive(f, value[f.key])).length;
164
+ const hasActive = activeCount > 0 || Boolean(searchValue);
165
+
166
+ function set(key, val) {
167
+ onChange?.({ ...value, [key]: val });
168
+ }
169
+
170
+ function clearAll() {
171
+ const cleared = {};
172
+ filters.forEach((f) => { cleared[f.key] = f.type === "multi" ? [] : ""; });
173
+ onChange?.(cleared);
174
+ onSearchChange?.("");
175
+ }
176
+
177
+ // ── Search field (shared between desktop and mobile) ──────────────────────
178
+
179
+ const searchField = hasSearch && (
180
+ <div className="a1-dt-filters__search-wrap">
181
+ <Icon name="search" className="a1-dt-filters__search-icon" />
182
+ <input
183
+ type="search"
184
+ className="a1-dt-filters__search-input"
185
+ value={searchValue}
186
+ onChange={(e) => onSearchChange(e.target.value)}
187
+ placeholder="Search…"
188
+ aria-label={searchColumn
189
+ ? `Search in ${searchableColumns?.find((c) => c.key === searchColumn)?.label ?? searchColumn}`
190
+ : "Search all fields"}
191
+ />
192
+ {searchableColumns?.length > 0 && (
193
+ <div className="a1-dt-filters__scope-wrap">
194
+ <select
195
+ className="a1-dt-filters__scope-select"
196
+ value={searchColumn}
197
+ onChange={(e) => onSearchColumnChange?.(e.target.value)}
198
+ aria-label="Search in field"
199
+ >
200
+ <option value="">All fields</option>
201
+ {searchableColumns.map((col) => (
202
+ <option key={col.key} value={col.key}>{col.label}</option>
203
+ ))}
204
+ </select>
205
+ <Icon name="expand_more" className="a1-dt-filters__scope-icon" />
206
+ </div>
207
+ )}
208
+ </div>
209
+ );
210
+
211
+ return (
212
+ <div className={["a1-dt-filters", className].filter(Boolean).join(" ")}>
213
+
214
+ {/* ── Desktop: search + chip row (hidden at xs) ──────────────────── */}
215
+ <div className="a1-dt-filters__desktop">
216
+ {searchField}
217
+
218
+ {filters.length > 0 && (
219
+ <>
220
+ <span className="a1-dt-filters__label">Filters</span>
221
+ <div className="a1-dt-filters__chips">
222
+ {filters.map((f) => (
223
+ <FilterChip
224
+ key={f.key}
225
+ filter={f}
226
+ selected={value[f.key] ?? (f.type === "multi" ? [] : "")}
227
+ onSet={(v) => set(f.key, v)}
228
+ />
229
+ ))}
230
+ </div>
231
+ </>
232
+ )}
233
+
234
+ {hasActive && (
235
+ <Button variant="secondary" size="sm" onClick={clearAll} className="a1-dt-filters__clear-all">
236
+ Clear all
237
+ </Button>
238
+ )}
239
+ </div>
240
+
241
+ {/* ── Mobile / xs: search always visible + menu button ───────────── */}
242
+ <div className="a1-dt-filters__mobile">
243
+ {searchField}
244
+
245
+ {filters.length > 0 && (
246
+ <div ref={mobileTriggerRef} className="a1-dt-filters__mobile-trigger">
247
+ <Button
248
+ variant="secondary"
249
+ size="sm"
250
+ icon="filter_list"
251
+ onClick={() => setMobileOpen(true)}
252
+ >
253
+ Filters
254
+ {activeCount > 0 && (
255
+ <span
256
+ className="a1-dt-filters__mobile-count"
257
+ aria-label={`${activeCount} active`}
258
+ >
259
+ {activeCount}
260
+ </span>
261
+ )}
262
+ </Button>
263
+ </div>
264
+ )}
265
+
266
+ {hasActive && (
267
+ <Button variant="secondary" size="sm" onClick={clearAll}>
268
+ Clear all
269
+ </Button>
270
+ )}
271
+
272
+ {filters.length > 0 && (
273
+ <Menu
274
+ open={mobileOpen}
275
+ anchorRef={mobileTriggerRef}
276
+ onClose={() => setMobileOpen(false)}
277
+ aria-label="Filters"
278
+ >
279
+ {filters.map((f) => {
280
+ const isMulti = f.type === "multi";
281
+ const val = value[f.key];
282
+ const arr = isMulti ? (Array.isArray(val) ? val : []) : null;
283
+
284
+ return (
285
+ <MenuSection key={f.key} label={f.label}>
286
+ {!isMulti && (
287
+ <MenuItem
288
+ key="__all__"
289
+ icon={radioIcon(!val)}
290
+ className={!val ? "a1-dt-filters__item--on" : ""}
291
+ onClick={() => set(f.key, "")}
292
+ >
293
+ All
294
+ </MenuItem>
295
+ )}
296
+ {f.options.map((opt) => {
297
+ const checked = isMulti ? arr.includes(opt.value) : val === opt.value;
298
+ const icon = isMulti ? checkIcon(checked) : radioIcon(checked);
299
+ return (
300
+ <MenuItem
301
+ key={opt.value}
302
+ icon={icon}
303
+ className={checked ? "a1-dt-filters__item--on" : ""}
304
+ onClick={() => {
305
+ if (isMulti) {
306
+ const next = arr.includes(opt.value)
307
+ ? arr.filter((v) => v !== opt.value)
308
+ : [...arr, opt.value];
309
+ set(f.key, next);
310
+ } else {
311
+ set(f.key, val === opt.value ? "" : opt.value);
312
+ }
313
+ }}
314
+ >
315
+ {opt.label}
316
+ </MenuItem>
317
+ );
318
+ })}
319
+ </MenuSection>
320
+ );
321
+ })}
322
+
323
+ {hasActive && (
324
+ <div className="a1-dt-filters__menu-clear">
325
+ <Button
326
+ variant="tertiary"
327
+ size="sm"
328
+ onClick={() => { clearAll(); setMobileOpen(false); }}
329
+ >
330
+ Clear all filters
331
+ </Button>
332
+ </div>
333
+ )}
334
+ </Menu>
335
+ )}
336
+ </div>
337
+ </div>
338
+ );
339
+ }
@@ -0,0 +1,259 @@
1
+ /* ═══════════════════════════════════════════════════════════════════════════
2
+ DataTableFilters
3
+ ═══════════════════════════════════════════════════════════════════════════ */
4
+
5
+ .a1-dt-filters {
6
+ min-height: 32px;
7
+ }
8
+
9
+ /* ── Desktop chip row (hidden at xs ≤480px) ──────────────────────────────── */
10
+
11
+ .a1-dt-filters__desktop {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--base-spacing-10);
15
+ flex-wrap: wrap;
16
+ }
17
+
18
+ /* ── Mobile layout (shown only at xs ≤480px) ─────────────────────────────── */
19
+
20
+ .a1-dt-filters__mobile {
21
+ display: none;
22
+ align-items: center;
23
+ gap: var(--base-spacing-8);
24
+ flex-wrap: wrap;
25
+ }
26
+
27
+ .a1-dt-filters__mobile-trigger {
28
+ display: contents;
29
+ }
30
+
31
+ @media (max-width: 480px) {
32
+ .a1-dt-filters__desktop { display: none; }
33
+ .a1-dt-filters__mobile { display: flex; }
34
+ }
35
+
36
+ /* ── "Filters" label ─────────────────────────────────────────────────────── */
37
+
38
+ .a1-dt-filters__label {
39
+ font-family: var(--component-paragraph-font-family);
40
+ font-size: var(--semantic-font-size-body-sm);
41
+ font-weight: 500;
42
+ color: var(--semantic-color-text-muted);
43
+ white-space: nowrap;
44
+ user-select: none;
45
+ margin-inline-start: var(--base-spacing-16);
46
+ }
47
+
48
+ /* ── Chip row ────────────────────────────────────────────────────────────── */
49
+
50
+ .a1-dt-filters__chips {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ gap: var(--base-spacing-6);
54
+ margin-inline-start: var(--base-spacing-6);
55
+ }
56
+
57
+ /* ── Chip + clear button wrapper ─────────────────────────────────────────── */
58
+
59
+ .a1-dt-filters__chip-wrap {
60
+ display: flex;
61
+ align-items: stretch;
62
+ }
63
+
64
+ /* ── Filter chip button ──────────────────────────────────────────────────── */
65
+
66
+ .a1-dt-filters__chip {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ gap: var(--base-spacing-4);
70
+ padding: var(--base-spacing-6) var(--base-spacing-12);
71
+ border: 1px solid var(--semantic-color-border-default);
72
+ border-radius: 9999px;
73
+ background: transparent;
74
+ font-family: var(--component-paragraph-font-family);
75
+ font-size: var(--semantic-font-size-body-sm);
76
+ color: var(--semantic-color-text-default);
77
+ cursor: pointer;
78
+ white-space: nowrap;
79
+ transition: background 100ms ease, border-color 100ms ease;
80
+ }
81
+
82
+ .a1-dt-filters__chip:hover {
83
+ background: var(--semantic-color-surface-raised);
84
+ }
85
+
86
+ .a1-dt-filters__chip:focus-visible {
87
+ outline: 2px solid var(--semantic-color-action-background);
88
+ outline-offset: 2px;
89
+ }
90
+
91
+ /* Active state — a value is selected */
92
+ .a1-dt-filters__chip--active {
93
+ border-color: var(--semantic-color-action-background);
94
+ color: var(--semantic-color-action-background);
95
+ }
96
+
97
+ .a1-dt-filters__chip-sep {
98
+ color: var(--semantic-color-text-muted);
99
+ font-weight: 400;
100
+ }
101
+
102
+ .a1-dt-filters__chip-value {
103
+ font-weight: 500;
104
+ }
105
+
106
+ .a1-dt-filters__chip-icon {
107
+ font-size: 18px;
108
+ flex-shrink: 0;
109
+ margin-inline-start: -2px;
110
+ }
111
+
112
+ /* ── Radio / checkbox icon color in open menu ────────────────────────────── */
113
+
114
+ .a1-dt-filters__item--on .a1-menu-item__icon {
115
+ color: var(--semantic-color-action-background);
116
+ }
117
+
118
+
119
+ /* ── Clear all button (desktop) ─────────────────────────────────────────── */
120
+
121
+ .a1-dt-filters__clear-all {
122
+ margin-inline-start: var(--base-spacing-16);
123
+ }
124
+
125
+ /* ── Mobile active-filter count badge ───────────────────────────────────── */
126
+
127
+ .a1-dt-filters__mobile-count {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ min-width: 18px;
132
+ height: 18px;
133
+ padding-inline: var(--base-spacing-4);
134
+ border-radius: 9999px;
135
+ background: var(--semantic-color-action-background);
136
+ color: var(--semantic-color-action-foreground);
137
+ font-size: 11px;
138
+ font-weight: 600;
139
+ line-height: 1;
140
+ margin-inline-start: var(--base-spacing-4);
141
+ }
142
+
143
+ /* ── Mobile menu clear section ───────────────────────────────────────────── */
144
+
145
+ .a1-dt-filters__menu-clear {
146
+ padding: var(--base-spacing-8) var(--base-spacing-16) var(--base-spacing-16);
147
+ }
148
+
149
+ /* ── Search field — matches compact TextField exactly ────────────────────── */
150
+
151
+ .a1-dt-filters__search-wrap {
152
+ position: relative;
153
+ display: flex;
154
+ align-items: center;
155
+ /* Compact field geometry */
156
+ height: var(--component-field-compact-height);
157
+ border: var(--component-field-border-width) solid var(--semantic-color-border-strong);
158
+ border-radius: var(--component-field-compact-border-radius);
159
+ background: var(--semantic-color-surface-page);
160
+ min-width: 200px;
161
+ overflow: hidden;
162
+ transition:
163
+ border-color var(--semantic-motion-duration-fast),
164
+ background var(--semantic-motion-duration-fast);
165
+ }
166
+
167
+ .a1-dt-filters__search-wrap:hover:not(:focus-within) {
168
+ background: var(--a1-field-hover-background);
169
+ border-color: var(--a1-field-hover-border-color);
170
+ }
171
+
172
+ .a1-dt-filters__search-wrap:focus-within {
173
+ border-color: var(--semantic-color-action-background);
174
+ outline: var(--component-field-focus-ring-width) solid var(--component-field-focus-ring-color);
175
+ outline-offset: var(--component-field-focus-ring-offset);
176
+ }
177
+
178
+ /* Search icon — 16px matches chevron-size-compact */
179
+ .a1-dt-filters__search-icon {
180
+ position: absolute;
181
+ left: var(--component-field-compact-padding-inline);
182
+ font-size: var(--component-field-chevron-size-compact);
183
+ color: var(--semantic-color-text-muted);
184
+ pointer-events: none;
185
+ flex-shrink: 0;
186
+ }
187
+
188
+ /* Input — fills remaining space; no individual border/outline */
189
+ .a1-dt-filters__search-input {
190
+ flex: 1;
191
+ min-width: 0;
192
+ height: 100%;
193
+ border: none;
194
+ background: transparent;
195
+ /* Left padding = field padding-inline + icon + gap (4px) */
196
+ padding-inline-start: calc(
197
+ var(--component-field-compact-padding-inline) +
198
+ var(--component-field-chevron-size-compact) +
199
+ var(--component-field-compact-gap)
200
+ );
201
+ padding-inline-end: var(--component-field-compact-padding-inline);
202
+ font-family: var(--component-paragraph-font-family);
203
+ font-size: var(--semantic-font-size-body-sm);
204
+ font-weight: var(--base-font-weight-regular);
205
+ color: var(--semantic-color-text-default);
206
+ outline: none;
207
+ }
208
+
209
+ .a1-dt-filters__search-input::-webkit-search-cancel-button { display: none; }
210
+
211
+ .a1-dt-filters__search-input::placeholder {
212
+ color: var(--semantic-color-text-muted);
213
+ font-weight: var(--base-font-weight-regular);
214
+ }
215
+
216
+ /* ── Scope selector (right side of search) ───────────────────────────────── */
217
+
218
+ .a1-dt-filters__scope-wrap {
219
+ position: relative;
220
+ display: flex;
221
+ align-items: center;
222
+ align-self: stretch;
223
+ flex-shrink: 0;
224
+ border-inline-start: var(--component-field-border-width) solid var(--semantic-color-border-subtle);
225
+ }
226
+
227
+ .a1-dt-filters__scope-select {
228
+ appearance: none;
229
+ -webkit-appearance: none;
230
+ height: 100%;
231
+ border: none;
232
+ background: var(--semantic-color-surface-panel);
233
+ /* Right padding accommodates the chevron icon */
234
+ padding-inline-start: var(--component-field-compact-padding-inline);
235
+ padding-inline-end: calc(var(--component-field-chevron-size-compact) + var(--component-field-compact-padding-inline));
236
+ font-family: var(--component-paragraph-font-family);
237
+ font-size: var(--semantic-font-size-body-xs);
238
+ color: var(--semantic-color-text-muted);
239
+ cursor: pointer;
240
+ max-width: 120px;
241
+ outline: none;
242
+ }
243
+
244
+ /* Chevron for scope select */
245
+ .a1-dt-filters__scope-icon {
246
+ position: absolute;
247
+ right: var(--component-field-compact-padding-inline);
248
+ font-size: var(--component-field-chevron-size-compact);
249
+ color: var(--semantic-color-text-muted);
250
+ pointer-events: none;
251
+ }
252
+
253
+ /* Mobile: search stretches to fill the row */
254
+ @media (max-width: 480px) {
255
+ .a1-dt-filters__search-wrap {
256
+ flex: 1 1 100%;
257
+ min-width: 0;
258
+ }
259
+ }