@robr0/design-system 0.2.0 → 0.4.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 (99) hide show
  1. package/README.md +33 -28
  2. package/components/Accordion/Accordion.js +0 -1
  3. package/components/AgentStatus/AgentStatus.css +436 -0
  4. package/components/AgentStatus/AgentStatus.d.ts +37 -0
  5. package/components/AgentStatus/AgentStatus.js +53 -0
  6. package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
  7. package/components/AgentStatus/AgentStatusPatterns.js +20 -0
  8. package/components/AlertDialog/AlertDialog.css +5 -5
  9. package/components/Avatar/Avatar.js +1 -0
  10. package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
  11. package/components/Breadcrumb/Breadcrumb.js +3 -2
  12. package/components/Button/Button.css +18 -0
  13. package/components/Button/Button.d.ts +20 -2
  14. package/components/Button/Button.js +13 -3
  15. package/components/ButtonGroup/ButtonGroup.js +2 -9
  16. package/components/Card/Card.css +6 -0
  17. package/components/Checkbox/Checkbox.d.ts +17 -3
  18. package/components/CircularButton/CircularButton.css +11 -0
  19. package/components/CircularButton/CircularButton.d.ts +15 -3
  20. package/components/CircularButton/CircularButton.js +19 -6
  21. package/components/CodeBlock/CodeBlock.js +10 -1
  22. package/components/ColorPicker/ColorPicker.css +278 -0
  23. package/components/ColorPicker/ColorPicker.d.ts +50 -0
  24. package/components/ColorPicker/ColorPicker.js +338 -0
  25. package/components/Combobox/Combobox.css +0 -36
  26. package/components/Combobox/Combobox.d.ts +10 -2
  27. package/components/Combobox/Combobox.js +90 -82
  28. package/components/CommandPalette/CommandPalette.css +1 -15
  29. package/components/CommandPalette/CommandPalette.js +6 -5
  30. package/components/ContextMenu/ContextMenu.css +258 -0
  31. package/components/ContextMenu/ContextMenu.d.ts +26 -0
  32. package/components/ContextMenu/ContextMenu.js +350 -0
  33. package/components/DateInput/DateInput.css +0 -36
  34. package/components/DateInput/DateInput.d.ts +5 -1
  35. package/components/DateInput/DateInput.js +37 -33
  36. package/components/DatePicker/DatePicker.js +45 -34
  37. package/components/Dialog/Dialog.css +5 -5
  38. package/components/Dialog/Dialog.js +5 -4
  39. package/components/Drawer/Drawer.js +7 -2
  40. package/components/Dropdown/Dropdown.css +2 -40
  41. package/components/Dropdown/Dropdown.d.ts +10 -2
  42. package/components/Dropdown/Dropdown.js +119 -109
  43. package/components/DropdownMenu/DropdownMenu.css +2 -4
  44. package/components/DropdownMenu/DropdownMenu.js +10 -7
  45. package/components/Field/Field.css +80 -0
  46. package/components/Field/Field.d.ts +50 -0
  47. package/components/Field/Field.js +58 -0
  48. package/components/Field/FieldContext.d.ts +42 -0
  49. package/components/Field/FieldContext.js +7 -0
  50. package/components/FileInput/FileInput.css +0 -36
  51. package/components/FileInput/FileInput.d.ts +5 -1
  52. package/components/FileInput/FileInput.js +111 -103
  53. package/components/Input/Input.css +0 -43
  54. package/components/Input/Input.d.ts +5 -1
  55. package/components/Input/Input.js +51 -46
  56. package/components/Kbd/Kbd.css +28 -0
  57. package/components/Kbd/Kbd.d.ts +19 -0
  58. package/components/Kbd/Kbd.js +14 -0
  59. package/components/LinkList/LinkList.d.ts +1 -0
  60. package/components/Popover/Popover.d.ts +0 -6
  61. package/components/Popover/Popover.js +22 -16
  62. package/components/ProgressBar/ProgressBar.js +1 -1
  63. package/components/RadioButton/RadioButton.d.ts +17 -3
  64. package/components/Reasoning/Reasoning.css +152 -0
  65. package/components/Reasoning/Reasoning.d.ts +36 -0
  66. package/components/Reasoning/Reasoning.js +69 -0
  67. package/components/SelectionCard/SelectionCard.css +6 -2
  68. package/components/SelectionCard/SelectionCard.d.ts +5 -1
  69. package/components/Skeleton/Skeleton.css +2 -0
  70. package/components/Slider/Slider.d.ts +5 -1
  71. package/components/Spinner/Spinner.css +11 -0
  72. package/components/Spinner/Spinner.d.ts +2 -2
  73. package/components/Swatch/Swatch.css +52 -0
  74. package/components/Swatch/Swatch.d.ts +34 -0
  75. package/components/Swatch/Swatch.js +44 -0
  76. package/components/Table/Table.css +19 -0
  77. package/components/Table/Table.js +1 -1
  78. package/components/Textarea/Textarea.css +0 -43
  79. package/components/Textarea/Textarea.d.ts +5 -1
  80. package/components/Textarea/Textarea.js +37 -35
  81. package/components/Timeline/Timeline.d.ts +2 -0
  82. package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
  83. package/components/ToggleSwitch/ToggleSwitch.css +7 -2
  84. package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
  85. package/components/ToolCall/ToolCall.css +213 -0
  86. package/components/ToolCall/ToolCall.d.ts +43 -0
  87. package/components/ToolCall/ToolCall.js +89 -0
  88. package/components/registry.d.ts +47 -10
  89. package/components/registry.js +5 -1
  90. package/components/registry.json +523 -56
  91. package/components/registry.json.d.ts +523 -56
  92. package/components/registry.json.js +4 -1
  93. package/index.d.ts +12 -1
  94. package/index.js +24 -1
  95. package/package.json +8 -4
  96. package/tokens/registry.json +1 -0
  97. package/tokens/registry.json.d.ts +1 -0
  98. package/tokens/registry.json.js +1 -1
  99. package/tokens/tokens-motion.css +3 -1
@@ -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);
@@ -290,10 +254,8 @@
290
254
  letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
291
255
  }
292
256
 
293
- .ds-dropdown--compact .ds-dropdown__group-label {
294
- font-size: var(--font-caption-size);
295
- line-height: var(--font-caption-line-height);
296
- }
257
+ /* Compact group labels keep the base --font-paragraph-sm-* type — the
258
+ scale has no smaller step. */
297
259
 
298
260
  .ds-dropdown--compact .ds-dropdown__check {
299
261
  --icon-size: var(--icon-size-sm);
@@ -39,9 +39,17 @@ type DropdownOwnProps = {
39
39
  onValueChange?: (value: string) => void;
40
40
  /** Additional CSS classes */
41
41
  className?: string;
42
- /** @deprecated Use `onValueChange` instead. */
42
+ /**
43
+ * Legacy change handler, kept for backwards compatibility.
44
+ *
45
+ * @deprecated Use `onValueChange` instead.
46
+ */
43
47
  onChange?: (value: string) => void;
44
- /** @deprecated Pass the native `aria-label` attribute instead. */
48
+ /**
49
+ * Legacy accessible-name prop.
50
+ *
51
+ * @deprecated Pass the native `aria-label` attribute instead.
52
+ */
45
53
  ariaLabel?: string;
46
54
  /**
47
55
  * Used only as a fallback for deriving the element id (`id || name || label`).
@@ -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";
@@ -96,10 +96,8 @@
96
96
  margin: var(--padding-xxs) var(--padding-sm-md);
97
97
  }
98
98
 
99
- /* Compact shortcuts */
100
- .ds-dropdown-menu__panel--compact .ds-dropdown-menu__shortcut {
101
- font-size: var(--font-caption-size);
102
- }
99
+ /* Compact shortcuts keep the base --font-paragraph-sm-* size — the scale
100
+ has no smaller step. */
103
101
 
104
102
  /* ============================================
105
103
  SUB-MENU PANEL
@@ -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
+ }
@@ -0,0 +1,50 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Field itself — everything else falls through to the wrapper. */
3
+ type FieldOwnProps = {
4
+ /** Label text rendered above the control */
5
+ label?: string;
6
+ /** The form control this field labels */
7
+ children?: React.ReactNode;
8
+ /** Helper or error message rendered below the control */
9
+ helperText?: string;
10
+ /**
11
+ * Optional content rendered opposite the helper text — a character counter,
12
+ * a unit, a "0/280". Present only when supplied; without it the helper sits
13
+ * directly in the field's column and no extra wrapper is introduced.
14
+ */
15
+ aside?: React.ReactNode;
16
+ /** Error state — recolours the helper text and marks the control invalid */
17
+ error?: boolean;
18
+ /** Marks the field required and renders the required marker */
19
+ required?: boolean;
20
+ /** Whether the control is disabled — dims the label */
21
+ disabled?: boolean;
22
+ /** Component size */
23
+ size?: 'default' | 'compact';
24
+ /**
25
+ * id for the control. Generated when omitted, so the label/control/helper
26
+ * association works with no configuration.
27
+ */
28
+ id?: string;
29
+ /** Additional CSS classes */
30
+ className?: string;
31
+ };
32
+ export interface FieldProps extends FieldOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof FieldOwnProps> {
33
+ }
34
+ /**
35
+ * Field owns the scaffolding shared by every labelled form control: the label
36
+ * and its `htmlFor`, the required marker, the helper/error message, the ids
37
+ * that tie them together, and the `aria-describedby` / `aria-invalid` wiring.
38
+ *
39
+ * Controls compose inside it and read the wiring through `useField()` rather
40
+ * than deriving ids themselves — which is what keeps accessibility correct
41
+ * systemically instead of one component at a time.
42
+ *
43
+ * ```tsx
44
+ * <Field label="Email" helperText="We never share it." required>
45
+ * <MyControl />
46
+ * </Field>
47
+ * ```
48
+ */
49
+ export declare const Field: React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>>;
50
+ export {};
@@ -0,0 +1,58 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import React, { useId } from "react";
3
+ import { FieldContext } from "./FieldContext.js";
4
+ import "./Field.css";
5
+ const Field = React.forwardRef(
6
+ ({
7
+ label,
8
+ children,
9
+ helperText,
10
+ aside,
11
+ error = false,
12
+ required = false,
13
+ disabled = false,
14
+ size = "default",
15
+ id,
16
+ className = "",
17
+ ...rest
18
+ }, ref) => {
19
+ const baseClass = "ds-field";
20
+ const generatedId = useId();
21
+ const controlId = id || generatedId;
22
+ const describedBy = helperText ? `${controlId}-helper` : void 0;
23
+ const labelId = label ? `${controlId}-label` : void 0;
24
+ const classes = [
25
+ baseClass,
26
+ size === "compact" ? `${baseClass}--compact` : "",
27
+ error ? `${baseClass}--error` : "",
28
+ disabled ? `${baseClass}--disabled` : "",
29
+ className
30
+ ].filter(Boolean).join(" ");
31
+ const context = {
32
+ controlId,
33
+ labelId,
34
+ describedBy,
35
+ invalid: error,
36
+ required,
37
+ disabled
38
+ };
39
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
40
+ label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, id: labelId, htmlFor: controlId, children: [
41
+ label,
42
+ required && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: [
43
+ " ",
44
+ "*"
45
+ ] })
46
+ ] }),
47
+ children,
48
+ aside ? /* @__PURE__ */ jsxs("div", { className: `${baseClass}__footer`, children: [
49
+ helperText ? /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: describedBy, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
50
+ aside
51
+ ] }) : helperText && /* @__PURE__ */ jsx("p", { className: `${baseClass}__helper`, id: describedBy, children: helperText })
52
+ ] }) });
53
+ }
54
+ );
55
+ Field.displayName = "Field";
56
+ export {
57
+ Field
58
+ };
@@ -0,0 +1,42 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * Everything a labelled form control needs to be announced correctly.
4
+ * Derived once by `Field`; read by a control via `useField()`.
5
+ *
6
+ * Lives in its own module so `Field.tsx` exports only a component — the
7
+ * react-refresh lint rule that keeps Fast Refresh working.
8
+ */
9
+ export interface FieldContextValue {
10
+ /** id for the control itself — the label's `htmlFor` points at it */
11
+ controlId: string;
12
+ /**
13
+ * id of the rendered `<label>`, or undefined when there is no label.
14
+ *
15
+ * `htmlFor` only associates with *labelable* elements (input, select,
16
+ * textarea, button…). A composite widget built from a `div` — a
17
+ * `role="combobox"` trigger, for instance — is not labelable, so it must
18
+ * point `aria-labelledby` at this id instead.
19
+ */
20
+ labelId: string | undefined;
21
+ /** id of the helper/error text, or undefined when there is none */
22
+ describedBy: string | undefined;
23
+ /** whether the control should report itself invalid */
24
+ invalid: boolean;
25
+ /** whether the control is required */
26
+ required: boolean;
27
+ /** whether the control is disabled */
28
+ disabled: boolean;
29
+ }
30
+ export declare const FieldContext: React.Context<FieldContextValue | null>;
31
+ /**
32
+ * Read the wiring `Field` derived.
33
+ *
34
+ * Returns `null` outside a `Field`, so a control that uses it still renders
35
+ * standalone — always optional-chain the result.
36
+ *
37
+ * ```tsx
38
+ * const field = useField();
39
+ * <input id={field?.controlId} aria-describedby={field?.describedBy} />
40
+ * ```
41
+ */
42
+ export declare const useField: () => FieldContextValue | null;
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ const FieldContext = React.createContext(null);
3
+ const useField = () => React.useContext(FieldContext);
4
+ export {
5
+ FieldContext,
6
+ useField
7
+ };