@robr0/design-system 0.2.0 → 0.3.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 (66) hide show
  1. package/README.md +8 -6
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/Avatar/Avatar.js +1 -0
  4. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  5. package/components/Breadcrumb/Breadcrumb.js +3 -2
  6. package/components/Button/Button.css +18 -0
  7. package/components/Button/Button.d.ts +6 -0
  8. package/components/Button/Button.js +13 -3
  9. package/components/Card/Card.css +6 -0
  10. package/components/CircularButton/CircularButton.css +11 -0
  11. package/components/CircularButton/CircularButton.d.ts +7 -1
  12. package/components/CircularButton/CircularButton.js +15 -4
  13. package/components/CodeBlock/CodeBlock.js +10 -1
  14. package/components/ColorPicker/ColorPicker.css +278 -0
  15. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  16. package/components/ColorPicker/ColorPicker.js +338 -0
  17. package/components/Combobox/Combobox.css +0 -36
  18. package/components/Combobox/Combobox.js +90 -82
  19. package/components/CommandPalette/CommandPalette.css +1 -15
  20. package/components/CommandPalette/CommandPalette.js +6 -5
  21. package/components/ContextMenu/ContextMenu.css +262 -0
  22. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  23. package/components/ContextMenu/ContextMenu.js +350 -0
  24. package/components/DateInput/DateInput.css +0 -36
  25. package/components/DateInput/DateInput.js +37 -33
  26. package/components/DatePicker/DatePicker.js +45 -34
  27. package/components/Dialog/Dialog.js +5 -4
  28. package/components/Drawer/Drawer.js +7 -2
  29. package/components/Dropdown/Dropdown.css +0 -36
  30. package/components/Dropdown/Dropdown.js +119 -109
  31. package/components/DropdownMenu/DropdownMenu.js +10 -7
  32. package/components/Field/Field.css +80 -0
  33. package/components/Field/Field.d.ts +50 -0
  34. package/components/Field/Field.js +58 -0
  35. package/components/Field/FieldContext.d.ts +42 -0
  36. package/components/Field/FieldContext.js +7 -0
  37. package/components/FileInput/FileInput.css +0 -36
  38. package/components/FileInput/FileInput.js +111 -103
  39. package/components/Input/Input.css +0 -43
  40. package/components/Input/Input.js +51 -46
  41. package/components/Kbd/Kbd.css +28 -0
  42. package/components/Kbd/Kbd.d.ts +19 -0
  43. package/components/Kbd/Kbd.js +14 -0
  44. package/components/Popover/Popover.d.ts +0 -6
  45. package/components/Popover/Popover.js +22 -16
  46. package/components/ProgressBar/ProgressBar.js +1 -1
  47. package/components/SelectionCard/SelectionCard.css +6 -2
  48. package/components/Skeleton/Skeleton.css +2 -0
  49. package/components/Spinner/Spinner.css +11 -0
  50. package/components/Spinner/Spinner.d.ts +2 -2
  51. package/components/Swatch/Swatch.css +52 -0
  52. package/components/Swatch/Swatch.d.ts +34 -0
  53. package/components/Swatch/Swatch.js +44 -0
  54. package/components/Table/Table.css +19 -0
  55. package/components/Table/Table.js +1 -1
  56. package/components/Textarea/Textarea.css +0 -43
  57. package/components/Textarea/Textarea.js +37 -35
  58. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  59. package/components/registry.d.ts +47 -10
  60. package/components/registry.js +5 -1
  61. package/components/registry.json +498 -56
  62. package/components/registry.json.d.ts +498 -56
  63. package/components/registry.json.js +4 -1
  64. package/index.d.ts +8 -1
  65. package/index.js +16 -1
  66. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import React from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./DateInput.css";
4
5
  import "../../fonts/material-symbols.css";
5
6
  const DateInput = React.forwardRef(
@@ -35,40 +36,43 @@ const DateInput = React.forwardRef(
35
36
  onValueChange?.(e.target.value);
36
37
  };
37
38
  const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
38
- return /* @__PURE__ */ jsxs("div", { className: classes, children: [
39
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, htmlFor: inputId, children: [
39
+ return /* @__PURE__ */ jsx(
40
+ Field,
41
+ {
42
+ className: classes,
40
43
  label,
41
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
42
- " ",
43
- "*"
44
+ helperText,
45
+ error,
46
+ required,
47
+ disabled,
48
+ size,
49
+ id: inputId,
50
+ children: /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
51
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: "calendar_today" }),
52
+ /* @__PURE__ */ jsx(
53
+ "input",
54
+ {
55
+ ...rest,
56
+ ref,
57
+ className: `${baseClass}__field`,
58
+ type: "date",
59
+ id: inputId,
60
+ name,
61
+ value,
62
+ placeholder,
63
+ disabled,
64
+ required,
65
+ min,
66
+ max,
67
+ "aria-label": ariaLabel || rest["aria-label"] || label,
68
+ "aria-invalid": error,
69
+ "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
70
+ onChange: handleChange
71
+ }
72
+ )
44
73
  ] })
45
- ] }),
46
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__field-wrapper`, children: [
47
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: "calendar_today" }),
48
- /* @__PURE__ */ jsx(
49
- "input",
50
- {
51
- ...rest,
52
- ref,
53
- className: `${baseClass}__field`,
54
- type: "date",
55
- id: inputId,
56
- name,
57
- value,
58
- placeholder,
59
- disabled,
60
- required,
61
- min,
62
- max,
63
- "aria-label": ariaLabel || rest["aria-label"] || label,
64
- "aria-invalid": error,
65
- "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
66
- onChange: handleChange
67
- }
68
- )
69
- ] }),
70
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
71
- ] });
74
+ }
75
+ );
72
76
  }
73
77
  );
74
78
  DateInput.displayName = "DateInput";
@@ -125,40 +125,51 @@ const DatePicker = React.forwardRef(
125
125
  }
126
126
  )
127
127
  ] }),
128
- /* @__PURE__ */ jsxs("div", { className: `${baseClass}__grid`, role: "grid", children: [
129
- DAYS.map((day) => /* @__PURE__ */ jsx("div", { className: `${baseClass}__day-header`, role: "columnheader", children: day }, day)),
130
- cells.map((cell, idx) => {
131
- const isSelected = selectedDate && isSameDay(cell.date, selectedDate);
132
- const isToday = isSameDay(cell.date, today);
133
- const isCellDisabled = isDateDisabled(cell.date);
134
- const cellClasses = [
135
- `${baseClass}__cell`,
136
- !cell.inMonth ? `${baseClass}__cell--outside` : "",
137
- isSelected ? `${baseClass}__cell--selected` : "",
138
- isToday && !isSelected ? `${baseClass}__cell--today` : "",
139
- isCellDisabled ? `${baseClass}__cell--disabled` : ""
140
- ].filter(Boolean).join(" ");
141
- return /* @__PURE__ */ jsx(
142
- "button",
143
- {
144
- type: "button",
145
- className: cellClasses,
146
- onClick: () => handleSelect(cell),
147
- disabled: isCellDisabled,
148
- "aria-label": cell.date.toLocaleDateString(void 0, {
149
- weekday: "long",
150
- year: "numeric",
151
- month: "long",
152
- day: "numeric"
153
- }),
154
- "aria-selected": isSelected || void 0,
155
- role: "gridcell",
156
- children: cell.day
157
- },
158
- idx
159
- );
160
- })
161
- ] })
128
+ /* @__PURE__ */ jsxs(
129
+ "div",
130
+ {
131
+ className: `${baseClass}__grid`,
132
+ role: "group",
133
+ "aria-label": `${MONTHS[viewMonth]} ${viewYear}`,
134
+ children: [
135
+ DAYS.map((day) => (
136
+ // Each cell's aria-label already carries the full weekday name, so
137
+ // the visual column headers are redundant noise for a screen reader.
138
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__day-header`, "aria-hidden": "true", children: day }, day)
139
+ )),
140
+ cells.map((cell, idx) => {
141
+ const isSelected = selectedDate && isSameDay(cell.date, selectedDate);
142
+ const isToday = isSameDay(cell.date, today);
143
+ const isCellDisabled = isDateDisabled(cell.date);
144
+ const cellClasses = [
145
+ `${baseClass}__cell`,
146
+ !cell.inMonth ? `${baseClass}__cell--outside` : "",
147
+ isSelected ? `${baseClass}__cell--selected` : "",
148
+ isToday && !isSelected ? `${baseClass}__cell--today` : "",
149
+ isCellDisabled ? `${baseClass}__cell--disabled` : ""
150
+ ].filter(Boolean).join(" ");
151
+ return /* @__PURE__ */ jsx(
152
+ "button",
153
+ {
154
+ type: "button",
155
+ className: cellClasses,
156
+ onClick: () => handleSelect(cell),
157
+ disabled: isCellDisabled,
158
+ "aria-label": `${cell.date.toLocaleDateString(void 0, {
159
+ weekday: "long",
160
+ year: "numeric",
161
+ month: "long",
162
+ day: "numeric"
163
+ })}${isSelected ? ", selected" : ""}`,
164
+ "aria-current": isToday ? "date" : void 0,
165
+ children: cell.day
166
+ },
167
+ idx
168
+ );
169
+ })
170
+ ]
171
+ }
172
+ )
162
173
  ] });
163
174
  }
164
175
  );
@@ -42,9 +42,7 @@ const Dialog = React.forwardRef(
42
42
  if (!open) return;
43
43
  const panel = panelRef.current;
44
44
  if (panel) {
45
- const first = panel.querySelector(
46
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
47
- );
45
+ const first = panel.querySelector("button, [href], input, select, textarea") ?? panel.querySelector('[tabindex]:not([tabindex="-1"])');
48
46
  (first ?? panel).focus();
49
47
  }
50
48
  const handleKeyDown = (e) => {
@@ -133,7 +131,10 @@ const Dialog = React.forwardRef(
133
131
  }
134
132
  )
135
133
  ] }),
136
- children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
134
+ children && // Scrollable when content is long; without a tab stop a keyboard
135
+ // user cannot reach that scroll. The initial-focus query below
136
+ // still prefers a real control, so this does not steal focus.
137
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, tabIndex: 0, children }),
137
138
  footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
138
139
  ]
139
140
  }
@@ -44,7 +44,9 @@ const Drawer = React.forwardRef(
44
44
  if (!open) return;
45
45
  const panel = panelRef.current;
46
46
  if (panel) {
47
- const first = panel.querySelector(FOCUSABLE);
47
+ const first = panel.querySelector(
48
+ "button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled])"
49
+ ) ?? panel.querySelector(FOCUSABLE);
48
50
  (first ?? panel).focus();
49
51
  }
50
52
  const handleKeyDown = (e) => {
@@ -132,7 +134,10 @@ const Drawer = React.forwardRef(
132
134
  }
133
135
  )
134
136
  ] }),
135
- children && /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children }),
137
+ children && // Scrollable when content is long; without a tab stop a keyboard
138
+ // user cannot reach that scroll. The initial-focus query below
139
+ // still prefers a real control, so this does not steal focus.
140
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, tabIndex: 0, children }),
136
141
  footer && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer`, children: footer })
137
142
  ]
138
143
  }
@@ -15,19 +15,6 @@
15
15
  LABEL
16
16
  ============================================ */
17
17
 
18
- .ds-dropdown__label {
19
- font-family: var(--font-paragraph-em-family);
20
- font-size: var(--font-paragraph-em-size);
21
- font-weight: var(--font-paragraph-em-weight);
22
- line-height: var(--font-paragraph-em-line-height);
23
- letter-spacing: var(--font-paragraph-em-letter-spacing);
24
- color: var(--color-text-primary);
25
- }
26
-
27
- .ds-dropdown__required {
28
- color: var(--color-core-accent-coral);
29
- }
30
-
31
18
  /* ============================================
32
19
  TRIGGER
33
20
  ============================================ */
@@ -216,16 +203,6 @@
216
203
  HELPER TEXT
217
204
  ============================================ */
218
205
 
219
- .ds-dropdown__helper {
220
- font-family: var(--font-paragraph-sm-family);
221
- font-size: var(--font-paragraph-sm-size);
222
- font-weight: var(--font-paragraph-sm-weight);
223
- line-height: var(--font-paragraph-sm-line-height);
224
- letter-spacing: var(--font-paragraph-sm-letter-spacing);
225
- color: var(--color-text-tertiary);
226
- margin: 0;
227
- }
228
-
229
206
  /* ============================================
230
207
  ERROR STATE
231
208
  ============================================ */
@@ -234,18 +211,10 @@
234
211
  border-color: var(--color-status-error-border);
235
212
  }
236
213
 
237
- .ds-dropdown--error .ds-dropdown__helper {
238
- color: var(--color-status-error-border);
239
- }
240
-
241
214
  /* ============================================
242
215
  DISABLED STATE
243
216
  ============================================ */
244
217
 
245
- .ds-dropdown--disabled .ds-dropdown__label {
246
- color: var(--color-input-text-disabled);
247
- }
248
-
249
218
  .ds-dropdown--disabled .ds-dropdown__trigger {
250
219
  background-color: var(--color-input-bg-disabled);
251
220
  border-color: var(--color-input-border-disabled);
@@ -278,11 +247,6 @@
278
247
  --icon-size: var(--icon-size-sm);
279
248
  }
280
249
 
281
- .ds-dropdown--compact .ds-dropdown__label {
282
- font-size: var(--font-paragraph-sm-size);
283
- line-height: var(--font-paragraph-sm-line-height);
284
- }
285
-
286
250
  .ds-dropdown--compact .ds-dropdown__option {
287
251
  padding: var(--padding-xs) var(--padding-sm); /* 6px 8px */
288
252
  font-size: var(--font-paragraph-sm-em-size);
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import React, { useState, useRef, useCallback, useEffect } from "react";
3
+ import { Field } from "../Field/Field.js";
3
4
  import "./Dropdown.css";
4
5
  import "../../fonts/material-symbols.css";
5
6
  const Dropdown = React.forwardRef(
@@ -112,122 +113,131 @@ const Dropdown = React.forwardRef(
112
113
  focusedEl?.scrollIntoView({ block: "nearest" });
113
114
  }
114
115
  }, [focusedIndex, isOpen]);
115
- return /* @__PURE__ */ jsxs("div", { ...rest, className: classes, ref: setRootRef, children: [
116
- label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, id: `${inputId}-label`, children: [
116
+ return /* @__PURE__ */ jsxs(
117
+ Field,
118
+ {
119
+ ...rest,
120
+ className: classes,
121
+ ref: setRootRef,
117
122
  label,
118
- required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
119
- " ",
120
- "*"
121
- ] })
122
- ] }),
123
- /* @__PURE__ */ jsxs(
124
- "div",
125
- {
126
- className: `${baseClass}__trigger`,
127
- role: "combobox",
128
- "aria-expanded": isOpen,
129
- "aria-haspopup": "listbox",
130
- "aria-labelledby": label ? `${inputId}-label` : void 0,
131
- "aria-label": ariaLabel || rest["aria-label"] || (!label ? placeholder : void 0),
132
- "aria-controls": `${inputId}-listbox`,
133
- tabIndex: disabled ? -1 : 0,
134
- onClick: handleToggle,
135
- onKeyDown: handleKeyDown,
136
- children: [
137
- /* @__PURE__ */ jsx(
138
- "span",
139
- {
140
- className: `${baseClass}__value ${!selectedOption ? `${baseClass}__value--placeholder` : ""}`,
141
- children: selectedOption ? selectedOption.label : placeholder
142
- }
143
- ),
144
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
145
- ]
146
- }
147
- ),
148
- isOpen && /* @__PURE__ */ jsx(
149
- "ul",
150
- {
151
- className: [`${baseClass}__menu`, isGrouped ? `${baseClass}__menu--grouped` : ""].filter(Boolean).join(" "),
152
- role: "listbox",
153
- id: `${inputId}-listbox`,
154
- ref: listRef,
155
- "aria-labelledby": label ? `${inputId}-label` : void 0,
156
- children: isGrouped ? (() => {
157
- let flatIndex = 0;
158
- return groups.map((group, groupIdx) => /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
159
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: group.label }),
160
- /* @__PURE__ */ jsx(
161
- "ul",
162
- {
163
- className: `${baseClass}__group-list`,
164
- role: "group",
165
- "aria-label": group.label,
166
- children: group.options.map((option) => {
167
- const myIndex = flatIndex++;
168
- return /* @__PURE__ */ jsxs(
169
- "li",
170
- {
171
- className: [
172
- `${baseClass}__option`,
173
- option.value === value ? `${baseClass}__option--selected` : "",
174
- option.disabled ? `${baseClass}__option--disabled` : "",
175
- myIndex === focusedIndex ? `${baseClass}__option--focused` : ""
176
- ].filter(Boolean).join(" "),
177
- role: "option",
178
- "aria-selected": option.value === value,
179
- "aria-disabled": option.disabled,
180
- onClick: () => !option.disabled && handleSelect(option.value),
181
- children: [
182
- option.label,
183
- option.value === value && /* @__PURE__ */ jsx(
184
- "span",
185
- {
186
- className: `${baseClass}__check material-symbols-rounded`,
187
- "aria-hidden": "true",
188
- children: "check"
189
- }
190
- )
191
- ]
192
- },
193
- option.value
194
- );
195
- })
196
- }
197
- ),
198
- groupIdx < groups.length - 1 && /* @__PURE__ */ jsx("li", { className: `${baseClass}__separator`, role: "separator" })
199
- ] }, `group-${groupIdx}`));
200
- })() : options.map((option, index) => /* @__PURE__ */ jsxs(
201
- "li",
123
+ helperText,
124
+ error,
125
+ required,
126
+ disabled,
127
+ size,
128
+ id: inputId,
129
+ children: [
130
+ /* @__PURE__ */ jsxs(
131
+ "div",
202
132
  {
203
- className: [
204
- `${baseClass}__option`,
205
- option.value === value ? `${baseClass}__option--selected` : "",
206
- option.disabled ? `${baseClass}__option--disabled` : "",
207
- index === focusedIndex ? `${baseClass}__option--focused` : ""
208
- ].filter(Boolean).join(" "),
209
- role: "option",
210
- "aria-selected": option.value === value,
211
- "aria-disabled": option.disabled,
212
- onClick: () => !option.disabled && handleSelect(option.value),
133
+ className: `${baseClass}__trigger`,
134
+ role: "combobox",
135
+ "aria-expanded": isOpen,
136
+ "aria-haspopup": "listbox",
137
+ "aria-labelledby": label ? `${inputId}-label` : void 0,
138
+ "aria-label": ariaLabel || rest["aria-label"] || (!label ? placeholder : void 0),
139
+ "aria-controls": `${inputId}-listbox`,
140
+ "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
141
+ "aria-invalid": error || void 0,
142
+ tabIndex: disabled ? -1 : 0,
143
+ onClick: handleToggle,
144
+ onKeyDown: handleKeyDown,
213
145
  children: [
214
- option.label,
215
- option.value === value && /* @__PURE__ */ jsx(
146
+ /* @__PURE__ */ jsx(
216
147
  "span",
217
148
  {
218
- className: `${baseClass}__check material-symbols-rounded`,
219
- "aria-hidden": "true",
220
- children: "check"
149
+ className: `${baseClass}__value ${!selectedOption ? `${baseClass}__value--placeholder` : ""}`,
150
+ children: selectedOption ? selectedOption.label : placeholder
221
151
  }
222
- )
152
+ ),
153
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
223
154
  ]
224
- },
225
- option.value
226
- ))
227
- }
228
- ),
229
- helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: `${inputId}-helper`, children: helperText })
230
- ] });
155
+ }
156
+ ),
157
+ isOpen && /* @__PURE__ */ jsx(
158
+ "ul",
159
+ {
160
+ className: [`${baseClass}__menu`, isGrouped ? `${baseClass}__menu--grouped` : ""].filter(Boolean).join(" "),
161
+ role: "listbox",
162
+ id: `${inputId}-listbox`,
163
+ ref: listRef,
164
+ "aria-labelledby": label ? `${inputId}-label` : void 0,
165
+ children: isGrouped ? (() => {
166
+ let flatIndex = 0;
167
+ return groups.map((group, groupIdx) => /* @__PURE__ */ jsxs("li", { className: `${baseClass}__group`, role: "none", children: [
168
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__group-label`, role: "presentation", children: group.label }),
169
+ /* @__PURE__ */ jsx(
170
+ "ul",
171
+ {
172
+ className: `${baseClass}__group-list`,
173
+ role: "group",
174
+ "aria-label": group.label,
175
+ children: group.options.map((option) => {
176
+ const myIndex = flatIndex++;
177
+ return /* @__PURE__ */ jsxs(
178
+ "li",
179
+ {
180
+ className: [
181
+ `${baseClass}__option`,
182
+ option.value === value ? `${baseClass}__option--selected` : "",
183
+ option.disabled ? `${baseClass}__option--disabled` : "",
184
+ myIndex === focusedIndex ? `${baseClass}__option--focused` : ""
185
+ ].filter(Boolean).join(" "),
186
+ role: "option",
187
+ "aria-selected": option.value === value,
188
+ "aria-disabled": option.disabled,
189
+ onClick: () => !option.disabled && handleSelect(option.value),
190
+ children: [
191
+ option.label,
192
+ option.value === value && /* @__PURE__ */ jsx(
193
+ "span",
194
+ {
195
+ className: `${baseClass}__check material-symbols-rounded`,
196
+ "aria-hidden": "true",
197
+ children: "check"
198
+ }
199
+ )
200
+ ]
201
+ },
202
+ option.value
203
+ );
204
+ })
205
+ }
206
+ ),
207
+ groupIdx < groups.length - 1 && /* @__PURE__ */ jsx("li", { className: `${baseClass}__separator`, role: "separator" })
208
+ ] }, `group-${groupIdx}`));
209
+ })() : options.map((option, index) => /* @__PURE__ */ jsxs(
210
+ "li",
211
+ {
212
+ className: [
213
+ `${baseClass}__option`,
214
+ option.value === value ? `${baseClass}__option--selected` : "",
215
+ option.disabled ? `${baseClass}__option--disabled` : "",
216
+ index === focusedIndex ? `${baseClass}__option--focused` : ""
217
+ ].filter(Boolean).join(" "),
218
+ role: "option",
219
+ "aria-selected": option.value === value,
220
+ "aria-disabled": option.disabled,
221
+ onClick: () => !option.disabled && handleSelect(option.value),
222
+ children: [
223
+ option.label,
224
+ option.value === value && /* @__PURE__ */ jsx(
225
+ "span",
226
+ {
227
+ className: `${baseClass}__check material-symbols-rounded`,
228
+ "aria-hidden": "true",
229
+ children: "check"
230
+ }
231
+ )
232
+ ]
233
+ },
234
+ option.value
235
+ ))
236
+ }
237
+ )
238
+ ]
239
+ }
240
+ );
231
241
  }
232
242
  );
233
243
  Dropdown.displayName = "Dropdown";
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useState, useRef, useCallback, useEffect } from "react";
2
+ import React, { useState, useRef, useCallback, useEffect } from "react";
3
3
  import "./DropdownMenu.css";
4
4
  import "../../fonts/material-symbols.css";
5
5
  function collectItems(entries) {
@@ -291,19 +291,22 @@ const DropdownMenu = ({
291
291
  ].filter(Boolean).join(" ");
292
292
  const itemCounter = { count: 0 };
293
293
  return /* @__PURE__ */ jsxs("div", { className: classes, ref: rootRef, children: [
294
- /* @__PURE__ */ jsx(
295
- "div",
294
+ /* @__PURE__ */ jsx("div", { className: `${baseClass}__trigger`, children: React.isValidElement(trigger) ? React.cloneElement(trigger, {
295
+ "aria-haspopup": "menu",
296
+ "aria-expanded": isOpen,
297
+ onClick: handleToggle,
298
+ onKeyDown: handleKeyDown
299
+ }) : /* @__PURE__ */ jsx(
300
+ "button",
296
301
  {
297
- className: `${baseClass}__trigger`,
298
- role: "button",
299
- tabIndex: 0,
302
+ type: "button",
300
303
  "aria-haspopup": "menu",
301
304
  "aria-expanded": isOpen,
302
305
  onClick: handleToggle,
303
306
  onKeyDown: handleKeyDown,
304
307
  children: trigger
305
308
  }
306
- ),
309
+ ) }),
307
310
  isOpen && /* @__PURE__ */ jsx(
308
311
  "ul",
309
312
  {
@@ -0,0 +1,80 @@
1
+ /* ============================================
2
+ FIELD COMPONENT
3
+ Label, required marker, and helper/error text
4
+ shared by every labelled form control.
5
+
6
+ Layout (flex column, gap) deliberately stays on the
7
+ consuming component's own root class — Field only
8
+ owns the label and helper scaffolding, so adopting it
9
+ changes no spacing.
10
+ ============================================ */
11
+
12
+ /* ============================================
13
+ LABEL
14
+ ============================================ */
15
+
16
+ .ds-field__label {
17
+ font-family: var(--font-paragraph-em-family);
18
+ font-size: var(--font-paragraph-em-size);
19
+ font-weight: var(--font-paragraph-em-weight);
20
+ line-height: var(--font-paragraph-em-line-height);
21
+ letter-spacing: var(--font-paragraph-em-letter-spacing);
22
+ color: var(--color-text-primary);
23
+ }
24
+
25
+ .ds-field__required {
26
+ color: var(--color-core-accent-coral);
27
+ }
28
+
29
+ /* ============================================
30
+ FOOTER — only rendered when an `aside` is supplied
31
+ (a character counter, a unit). Without one the helper
32
+ sits directly in the field column, so adopting Field
33
+ adds no wrapper to existing markup.
34
+ ============================================ */
35
+
36
+ .ds-field__footer {
37
+ display: flex;
38
+ align-items: flex-start;
39
+ justify-content: space-between;
40
+ gap: var(--gap-sm);
41
+ }
42
+
43
+ /* ============================================
44
+ HELPER TEXT
45
+ ============================================ */
46
+
47
+ .ds-field__helper {
48
+ font-family: var(--font-paragraph-sm-family);
49
+ font-size: var(--font-paragraph-sm-size);
50
+ font-weight: var(--font-paragraph-sm-weight);
51
+ line-height: var(--font-paragraph-sm-line-height);
52
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
53
+ color: var(--color-text-tertiary);
54
+ margin: 0;
55
+ }
56
+
57
+ /* ============================================
58
+ ERROR STATE
59
+ ============================================ */
60
+
61
+ .ds-field--error .ds-field__helper {
62
+ color: var(--color-status-error-border);
63
+ }
64
+
65
+ /* ============================================
66
+ DISABLED STATE
67
+ ============================================ */
68
+
69
+ .ds-field--disabled .ds-field__label {
70
+ color: var(--color-input-text-disabled);
71
+ }
72
+
73
+ /* ============================================
74
+ COMPACT SIZE
75
+ ============================================ */
76
+
77
+ .ds-field--compact .ds-field__label {
78
+ font-size: var(--font-paragraph-sm-size);
79
+ line-height: var(--font-paragraph-sm-line-height);
80
+ }