@robr0/design-system 0.1.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.
- package/README.md +13 -9
- package/components/Accordion/Accordion.js +0 -1
- package/components/Avatar/Avatar.js +1 -0
- package/components/Breadcrumb/Breadcrumb.css +9 -1
- 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 +38 -16
- package/components/Button/Button.js +76 -58
- package/components/Card/Card.css +6 -0
- package/components/Card/Card.d.ts +17 -5
- package/components/Card/Card.js +101 -80
- package/components/Checkbox/Checkbox.d.ts +24 -12
- package/components/Checkbox/Checkbox.js +99 -85
- package/components/CircularButton/CircularButton.css +11 -0
- package/components/CircularButton/CircularButton.d.ts +7 -1
- package/components/CircularButton/CircularButton.js +15 -4
- 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 +19 -7
- package/components/Combobox/Combobox.js +106 -89
- package/components/CommandPalette/CommandPalette.css +1 -15
- package/components/CommandPalette/CommandPalette.js +6 -5
- package/components/ContextMenu/ContextMenu.css +262 -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 +18 -21
- package/components/DateInput/DateInput.js +76 -64
- package/components/DatePicker/DatePicker.d.ts +9 -2
- package/components/DatePicker/DatePicker.js +137 -128
- package/components/Dialog/Dialog.d.ts +15 -4
- package/components/Dialog/Dialog.js +136 -118
- package/components/Drawer/Drawer.d.ts +15 -4
- package/components/Drawer/Drawer.js +138 -119
- package/components/Dropdown/Dropdown.css +0 -36
- package/components/Dropdown/Dropdown.d.ts +24 -8
- package/components/Dropdown/Dropdown.js +219 -169
- 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 +13 -17
- package/components/FileInput/FileInput.js +168 -141
- package/components/Input/Input.css +0 -43
- package/components/Input/Input.d.ts +23 -22
- package/components/Input/Input.js +93 -66
- package/components/Kbd/Kbd.css +28 -0
- package/components/Kbd/Kbd.d.ts +19 -0
- package/components/Kbd/Kbd.js +14 -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 +26 -13
- package/components/RadioButton/RadioButton.js +95 -70
- package/components/SelectionCard/SelectionCard.css +6 -2
- package/components/SelectionCard/SelectionCard.d.ts +10 -4
- package/components/SelectionCard/SelectionCard.js +72 -56
- package/components/Skeleton/Skeleton.css +2 -0
- package/components/Slider/Slider.d.ts +19 -10
- package/components/Slider/Slider.js +50 -40
- 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.d.ts +10 -2
- package/components/Table/Table.js +58 -58
- package/components/Textarea/Textarea.css +0 -43
- package/components/Textarea/Textarea.d.ts +21 -22
- package/components/Textarea/Textarea.js +78 -67
- package/components/ToggleGroup/ToggleGroup.d.ts +18 -8
- package/components/ToggleGroup/ToggleGroup.js +61 -44
- package/components/ToggleSwitch/ToggleSwitch.css +7 -2
- package/components/ToggleSwitch/ToggleSwitch.d.ts +17 -9
- package/components/ToggleSwitch/ToggleSwitch.js +57 -44
- package/components/registry.d.ts +47 -10
- package/components/registry.js +5 -1
- package/components/registry.json +498 -56
- package/components/registry.json.d.ts +498 -56
- package/components/registry.json.js +4 -1
- package/index.d.ts +8 -1
- package/index.js +16 -1
- package/package.json +1 -1
|
@@ -1,156 +1,219 @@
|
|
|
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
|
+
import { Field } from "../Field/Field.js";
|
|
3
4
|
import "./Dropdown.css";
|
|
4
5
|
import "../../fonts/material-symbols.css";
|
|
5
|
-
const Dropdown = (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
6
|
+
const Dropdown = React.forwardRef(
|
|
7
|
+
({
|
|
8
|
+
label,
|
|
9
|
+
placeholder = "Select an option",
|
|
10
|
+
value,
|
|
11
|
+
options,
|
|
12
|
+
groups,
|
|
13
|
+
size = "default",
|
|
14
|
+
disabled = false,
|
|
15
|
+
required = false,
|
|
16
|
+
error = false,
|
|
17
|
+
helperText,
|
|
18
|
+
onValueChange,
|
|
19
|
+
onChange,
|
|
20
|
+
className = "",
|
|
21
|
+
ariaLabel,
|
|
22
|
+
name,
|
|
23
|
+
id,
|
|
24
|
+
...rest
|
|
25
|
+
}, ref) => {
|
|
26
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
27
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
28
|
+
const dropdownRef = useRef(null);
|
|
29
|
+
const listRef = useRef(null);
|
|
30
|
+
const setRootRef = (node) => {
|
|
31
|
+
dropdownRef.current = node;
|
|
32
|
+
if (typeof ref === "function") ref(node);
|
|
33
|
+
else if (ref) ref.current = node;
|
|
34
|
+
};
|
|
35
|
+
const baseClass = "ds-dropdown";
|
|
36
|
+
const isGrouped = groups && groups.length > 0;
|
|
37
|
+
const classes = [
|
|
38
|
+
baseClass,
|
|
39
|
+
size === "compact" ? `${baseClass}--compact` : "",
|
|
40
|
+
isOpen ? `${baseClass}--open` : "",
|
|
41
|
+
error ? `${baseClass}--error` : "",
|
|
42
|
+
disabled ? `${baseClass}--disabled` : "",
|
|
43
|
+
className
|
|
44
|
+
].filter(Boolean).join(" ");
|
|
45
|
+
const flatOptions = isGrouped ? groups.flatMap((g) => g.options) : options;
|
|
46
|
+
const selectedOption = flatOptions.find((opt) => opt.value === value);
|
|
47
|
+
const inputId = id || name || label?.toLowerCase().replace(/\s+/g, "-");
|
|
48
|
+
const handleToggle = () => {
|
|
49
|
+
if (!disabled) {
|
|
50
|
+
setIsOpen((prev) => !prev);
|
|
51
|
+
setFocusedIndex(-1);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const handleSelect = (optionValue) => {
|
|
55
|
+
onValueChange?.(optionValue);
|
|
56
|
+
onChange?.(optionValue);
|
|
50
57
|
setIsOpen(false);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
55
|
-
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
56
|
-
}, [handleClickOutside]);
|
|
57
|
-
const handleKeyDown = (e) => {
|
|
58
|
-
if (disabled) return;
|
|
59
|
-
switch (e.key) {
|
|
60
|
-
case "Enter":
|
|
61
|
-
case " ":
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
if (isOpen && focusedIndex >= 0 && !flatOptions[focusedIndex]?.disabled) {
|
|
64
|
-
handleSelect(flatOptions[focusedIndex].value);
|
|
65
|
-
} else {
|
|
66
|
-
setIsOpen((prev) => !prev);
|
|
67
|
-
}
|
|
68
|
-
break;
|
|
69
|
-
case "ArrowDown":
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
if (!isOpen) {
|
|
72
|
-
setIsOpen(true);
|
|
73
|
-
} else {
|
|
74
|
-
setFocusedIndex((prev) => {
|
|
75
|
-
let next = prev + 1;
|
|
76
|
-
while (next < flatOptions.length && flatOptions[next].disabled) next++;
|
|
77
|
-
return next < flatOptions.length ? next : prev;
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
break;
|
|
81
|
-
case "ArrowUp":
|
|
82
|
-
e.preventDefault();
|
|
83
|
-
if (isOpen) {
|
|
84
|
-
setFocusedIndex((prev) => {
|
|
85
|
-
let next = prev - 1;
|
|
86
|
-
while (next >= 0 && flatOptions[next].disabled) next--;
|
|
87
|
-
return next >= 0 ? next : prev;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
case "Escape":
|
|
92
|
-
setIsOpen(false);
|
|
93
|
-
break;
|
|
94
|
-
case "Tab":
|
|
58
|
+
};
|
|
59
|
+
const handleClickOutside = useCallback((e) => {
|
|
60
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
95
61
|
setIsOpen(false);
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
if (isOpen && focusedIndex >= 0 && listRef.current) {
|
|
101
|
-
const focusedEl = listRef.current.children[focusedIndex];
|
|
102
|
-
focusedEl?.scrollIntoView({ block: "nearest" });
|
|
103
|
-
}
|
|
104
|
-
}, [focusedIndex, isOpen]);
|
|
105
|
-
return /* @__PURE__ */ jsxs("div", { className: classes, ref: dropdownRef, children: [
|
|
106
|
-
label && /* @__PURE__ */ jsxs("label", { className: `${baseClass}__label`, id: `${inputId}-label`, children: [
|
|
107
|
-
label,
|
|
108
|
-
required && /* @__PURE__ */ jsx("span", { className: `${baseClass}__required`, "aria-hidden": "true", children: " *" })
|
|
109
|
-
] }),
|
|
110
|
-
/* @__PURE__ */ jsxs(
|
|
111
|
-
"div",
|
|
112
|
-
{
|
|
113
|
-
className: `${baseClass}__trigger`,
|
|
114
|
-
role: "combobox",
|
|
115
|
-
"aria-expanded": isOpen,
|
|
116
|
-
"aria-haspopup": "listbox",
|
|
117
|
-
"aria-labelledby": label ? `${inputId}-label` : void 0,
|
|
118
|
-
"aria-label": ariaLabel || (!label ? placeholder : void 0),
|
|
119
|
-
"aria-controls": `${inputId}-listbox`,
|
|
120
|
-
tabIndex: disabled ? -1 : 0,
|
|
121
|
-
onClick: handleToggle,
|
|
122
|
-
onKeyDown: handleKeyDown,
|
|
123
|
-
children: [
|
|
124
|
-
/* @__PURE__ */ jsx("span", { className: `${baseClass}__value ${!selectedOption ? `${baseClass}__value--placeholder` : ""}`, children: selectedOption ? selectedOption.label : placeholder }),
|
|
125
|
-
/* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
|
|
126
|
-
]
|
|
127
62
|
}
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
"
|
|
63
|
+
}, []);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
66
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
67
|
+
}, [handleClickOutside]);
|
|
68
|
+
const handleKeyDown = (e) => {
|
|
69
|
+
if (disabled) return;
|
|
70
|
+
switch (e.key) {
|
|
71
|
+
case "Enter":
|
|
72
|
+
case " ":
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
if (isOpen && focusedIndex >= 0 && !flatOptions[focusedIndex]?.disabled) {
|
|
75
|
+
handleSelect(flatOptions[focusedIndex].value);
|
|
76
|
+
} else {
|
|
77
|
+
setIsOpen((prev) => !prev);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case "ArrowDown":
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
if (!isOpen) {
|
|
83
|
+
setIsOpen(true);
|
|
84
|
+
} else {
|
|
85
|
+
setFocusedIndex((prev) => {
|
|
86
|
+
let next = prev + 1;
|
|
87
|
+
while (next < flatOptions.length && flatOptions[next].disabled) next++;
|
|
88
|
+
return next < flatOptions.length ? next : prev;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case "ArrowUp":
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
if (isOpen) {
|
|
95
|
+
setFocusedIndex((prev) => {
|
|
96
|
+
let next = prev - 1;
|
|
97
|
+
while (next >= 0 && flatOptions[next].disabled) next--;
|
|
98
|
+
return next >= 0 ? next : prev;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case "Escape":
|
|
103
|
+
setIsOpen(false);
|
|
104
|
+
break;
|
|
105
|
+
case "Tab":
|
|
106
|
+
setIsOpen(false);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (isOpen && focusedIndex >= 0 && listRef.current) {
|
|
112
|
+
const focusedEl = listRef.current.children[focusedIndex];
|
|
113
|
+
focusedEl?.scrollIntoView({ block: "nearest" });
|
|
114
|
+
}
|
|
115
|
+
}, [focusedIndex, isOpen]);
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
Field,
|
|
131
118
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
119
|
+
...rest,
|
|
120
|
+
className: classes,
|
|
121
|
+
ref: setRootRef,
|
|
122
|
+
label,
|
|
123
|
+
helperText,
|
|
124
|
+
error,
|
|
125
|
+
required,
|
|
126
|
+
disabled,
|
|
127
|
+
size,
|
|
128
|
+
id: inputId,
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ jsxs(
|
|
131
|
+
"div",
|
|
132
|
+
{
|
|
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,
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
"span",
|
|
148
|
+
{
|
|
149
|
+
className: `${baseClass}__value ${!selectedOption ? `${baseClass}__value--placeholder` : ""}`,
|
|
150
|
+
children: selectedOption ? selectedOption.label : placeholder
|
|
151
|
+
}
|
|
152
|
+
),
|
|
153
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
|
|
154
|
+
]
|
|
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(
|
|
147
210
|
"li",
|
|
148
211
|
{
|
|
149
212
|
className: [
|
|
150
213
|
`${baseClass}__option`,
|
|
151
214
|
option.value === value ? `${baseClass}__option--selected` : "",
|
|
152
215
|
option.disabled ? `${baseClass}__option--disabled` : "",
|
|
153
|
-
|
|
216
|
+
index === focusedIndex ? `${baseClass}__option--focused` : ""
|
|
154
217
|
].filter(Boolean).join(" "),
|
|
155
218
|
role: "option",
|
|
156
219
|
"aria-selected": option.value === value,
|
|
@@ -158,39 +221,26 @@ const Dropdown = ({
|
|
|
158
221
|
onClick: () => !option.disabled && handleSelect(option.value),
|
|
159
222
|
children: [
|
|
160
223
|
option.label,
|
|
161
|
-
option.value === value && /* @__PURE__ */ jsx(
|
|
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
|
+
)
|
|
162
232
|
]
|
|
163
233
|
},
|
|
164
234
|
option.value
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
})() : options.map((option, index) => /* @__PURE__ */ jsxs(
|
|
170
|
-
"li",
|
|
171
|
-
{
|
|
172
|
-
className: [
|
|
173
|
-
`${baseClass}__option`,
|
|
174
|
-
option.value === value ? `${baseClass}__option--selected` : "",
|
|
175
|
-
option.disabled ? `${baseClass}__option--disabled` : "",
|
|
176
|
-
index === focusedIndex ? `${baseClass}__option--focused` : ""
|
|
177
|
-
].filter(Boolean).join(" "),
|
|
178
|
-
role: "option",
|
|
179
|
-
"aria-selected": option.value === value,
|
|
180
|
-
"aria-disabled": option.disabled,
|
|
181
|
-
onClick: () => !option.disabled && handleSelect(option.value),
|
|
182
|
-
children: [
|
|
183
|
-
option.label,
|
|
184
|
-
option.value === value && /* @__PURE__ */ jsx("span", { className: `${baseClass}__check material-symbols-rounded`, "aria-hidden": "true", children: "check" })
|
|
185
|
-
]
|
|
186
|
-
},
|
|
187
|
-
option.value
|
|
188
|
-
))
|
|
235
|
+
))
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
]
|
|
189
239
|
}
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
Dropdown.displayName = "Dropdown";
|
|
194
244
|
export {
|
|
195
245
|
Dropdown
|
|
196
246
|
};
|
|
@@ -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
|
+
};
|