@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.
- package/README.md +33 -28
- package/components/Accordion/Accordion.js +0 -1
- package/components/AgentStatus/AgentStatus.css +436 -0
- package/components/AgentStatus/AgentStatus.d.ts +37 -0
- package/components/AgentStatus/AgentStatus.js +53 -0
- package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
- package/components/AgentStatus/AgentStatusPatterns.js +20 -0
- package/components/AlertDialog/AlertDialog.css +5 -5
- package/components/Avatar/Avatar.js +1 -0
- package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
- package/components/Breadcrumb/Breadcrumb.js +3 -2
- package/components/Button/Button.css +18 -0
- package/components/Button/Button.d.ts +20 -2
- package/components/Button/Button.js +13 -3
- package/components/ButtonGroup/ButtonGroup.js +2 -9
- package/components/Card/Card.css +6 -0
- package/components/Checkbox/Checkbox.d.ts +17 -3
- package/components/CircularButton/CircularButton.css +11 -0
- package/components/CircularButton/CircularButton.d.ts +15 -3
- package/components/CircularButton/CircularButton.js +19 -6
- package/components/CodeBlock/CodeBlock.js +10 -1
- package/components/ColorPicker/ColorPicker.css +278 -0
- package/components/ColorPicker/ColorPicker.d.ts +50 -0
- package/components/ColorPicker/ColorPicker.js +338 -0
- package/components/Combobox/Combobox.css +0 -36
- package/components/Combobox/Combobox.d.ts +10 -2
- package/components/Combobox/Combobox.js +90 -82
- package/components/CommandPalette/CommandPalette.css +1 -15
- package/components/CommandPalette/CommandPalette.js +6 -5
- package/components/ContextMenu/ContextMenu.css +258 -0
- package/components/ContextMenu/ContextMenu.d.ts +26 -0
- package/components/ContextMenu/ContextMenu.js +350 -0
- package/components/DateInput/DateInput.css +0 -36
- package/components/DateInput/DateInput.d.ts +5 -1
- package/components/DateInput/DateInput.js +37 -33
- package/components/DatePicker/DatePicker.js +45 -34
- package/components/Dialog/Dialog.css +5 -5
- package/components/Dialog/Dialog.js +5 -4
- package/components/Drawer/Drawer.js +7 -2
- package/components/Dropdown/Dropdown.css +2 -40
- package/components/Dropdown/Dropdown.d.ts +10 -2
- package/components/Dropdown/Dropdown.js +119 -109
- package/components/DropdownMenu/DropdownMenu.css +2 -4
- package/components/DropdownMenu/DropdownMenu.js +10 -7
- package/components/Field/Field.css +80 -0
- package/components/Field/Field.d.ts +50 -0
- package/components/Field/Field.js +58 -0
- package/components/Field/FieldContext.d.ts +42 -0
- package/components/Field/FieldContext.js +7 -0
- package/components/FileInput/FileInput.css +0 -36
- package/components/FileInput/FileInput.d.ts +5 -1
- package/components/FileInput/FileInput.js +111 -103
- package/components/Input/Input.css +0 -43
- package/components/Input/Input.d.ts +5 -1
- package/components/Input/Input.js +51 -46
- package/components/Kbd/Kbd.css +28 -0
- package/components/Kbd/Kbd.d.ts +19 -0
- package/components/Kbd/Kbd.js +14 -0
- package/components/LinkList/LinkList.d.ts +1 -0
- package/components/Popover/Popover.d.ts +0 -6
- package/components/Popover/Popover.js +22 -16
- package/components/ProgressBar/ProgressBar.js +1 -1
- package/components/RadioButton/RadioButton.d.ts +17 -3
- package/components/Reasoning/Reasoning.css +152 -0
- package/components/Reasoning/Reasoning.d.ts +36 -0
- package/components/Reasoning/Reasoning.js +69 -0
- package/components/SelectionCard/SelectionCard.css +6 -2
- package/components/SelectionCard/SelectionCard.d.ts +5 -1
- package/components/Skeleton/Skeleton.css +2 -0
- package/components/Slider/Slider.d.ts +5 -1
- package/components/Spinner/Spinner.css +11 -0
- package/components/Spinner/Spinner.d.ts +2 -2
- package/components/Swatch/Swatch.css +52 -0
- package/components/Swatch/Swatch.d.ts +34 -0
- package/components/Swatch/Swatch.js +44 -0
- package/components/Table/Table.css +19 -0
- package/components/Table/Table.js +1 -1
- package/components/Textarea/Textarea.css +0 -43
- package/components/Textarea/Textarea.d.ts +5 -1
- package/components/Textarea/Textarea.js +37 -35
- package/components/Timeline/Timeline.d.ts +2 -0
- package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
- package/components/ToggleSwitch/ToggleSwitch.css +7 -2
- package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
- package/components/ToolCall/ToolCall.css +213 -0
- package/components/ToolCall/ToolCall.d.ts +43 -0
- package/components/ToolCall/ToolCall.js +89 -0
- package/components/registry.d.ts +47 -10
- package/components/registry.js +5 -1
- package/components/registry.json +523 -56
- package/components/registry.json.d.ts +523 -56
- package/components/registry.json.js +4 -1
- package/index.d.ts +12 -1
- package/index.js +24 -1
- package/package.json +8 -4
- package/tokens/registry.json +1 -0
- package/tokens/registry.json.d.ts +1 -0
- package/tokens/registry.json.js +1 -1
- 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
|
-
|
|
294
|
-
|
|
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
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Legacy change handler, kept for backwards compatibility.
|
|
44
|
+
*
|
|
45
|
+
* @deprecated Use `onValueChange` instead.
|
|
46
|
+
*/
|
|
43
47
|
onChange?: (value: string) => void;
|
|
44
|
-
/**
|
|
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(
|
|
116
|
-
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
Field,
|
|
118
|
+
{
|
|
119
|
+
...rest,
|
|
120
|
+
className: classes,
|
|
121
|
+
ref: setRootRef,
|
|
117
122
|
label,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
]
|
|
209
|
-
|
|
210
|
-
"aria-
|
|
211
|
-
"aria-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
option.value === value && /* @__PURE__ */ jsx(
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
216
147
|
"span",
|
|
217
148
|
{
|
|
218
|
-
className: `${baseClass}
|
|
219
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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;
|