@robr0/design-system 0.7.0 → 0.9.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 +4 -4
- package/components/Accordion/Accordion.js +1 -0
- package/components/AgentPlan/AgentPlan.css +231 -0
- package/components/AgentPlan/AgentPlan.d.ts +41 -0
- package/components/AgentPlan/AgentPlan.js +100 -0
- package/components/AgentStatus/AgentStatus.js +1 -0
- package/components/AiButton/AiButton.js +1 -0
- package/components/AlertDialog/AlertDialog.js +1 -0
- package/components/AnchorNav/AnchorNav.css +63 -0
- package/components/AnchorNav/AnchorNav.d.ts +33 -0
- package/components/AnchorNav/AnchorNav.js +82 -0
- package/components/AppLayout/AppLayout.js +1 -0
- package/components/AppSidebar/AppSidebar.js +1 -0
- package/components/Avatar/Avatar.js +1 -0
- package/components/Button/Button.js +1 -0
- package/components/Card/Card.js +1 -0
- package/components/Carousel/Carousel.js +1 -0
- package/components/ChatThread/ChatThread.js +1 -0
- package/components/Checkbox/Checkbox.js +1 -0
- package/components/CodeBlock/CodeBlock.js +1 -0
- package/components/CodeDiff/CodeDiff.css +175 -0
- package/components/CodeDiff/CodeDiff.d.ts +58 -0
- package/components/CodeDiff/CodeDiff.js +114 -0
- package/components/ColorPicker/ColorPicker.js +1 -0
- package/components/Combobox/Combobox.js +1 -0
- package/components/CommandPalette/CommandPalette.js +1 -0
- package/components/Composer/Composer.js +1 -0
- package/components/ContactCard/ContactCard.js +1 -0
- package/components/ContextMenu/ContextMenu.js +1 -0
- package/components/DataTable/DataTable.css +105 -0
- package/components/DataTable/DataTable.d.ts +81 -0
- package/components/DataTable/DataTable.js +236 -0
- package/components/DateInput/DateInput.js +1 -0
- package/components/DatePicker/DatePicker.js +1 -0
- package/components/Dialog/Dialog.js +1 -0
- package/components/Drawer/Drawer.js +1 -0
- package/components/Dropdown/Dropdown.js +1 -0
- package/components/DropdownMenu/DropdownMenu.css +1 -1
- package/components/DropdownMenu/DropdownMenu.js +1 -0
- package/components/EventCalendar/EventCalendar.css +328 -0
- package/components/EventCalendar/EventCalendar.d.ts +51 -0
- package/components/EventCalendar/EventCalendar.js +214 -0
- package/components/Field/Field.js +1 -0
- package/components/Field/FieldContext.js +1 -0
- package/components/FileInput/FileInput.js +1 -0
- package/components/Input/Input.js +1 -0
- package/components/InterruptCard/InterruptCard.css +11 -21
- package/components/InterruptCard/InterruptCard.d.ts +5 -6
- package/components/MessageCard/MessageCard.css +1 -2
- package/components/ModelPicker/ModelPicker.css +240 -0
- package/components/ModelPicker/ModelPicker.d.ts +60 -0
- package/components/ModelPicker/ModelPicker.js +218 -0
- package/components/NavList/NavList.js +1 -0
- package/components/NotificationCenter/NotificationCenter.css +262 -0
- package/components/NotificationCenter/NotificationCenter.d.ts +72 -0
- package/components/NotificationCenter/NotificationCenter.js +129 -0
- package/components/NumberInput/NumberInput.css +154 -0
- package/components/NumberInput/NumberInput.d.ts +44 -0
- package/components/NumberInput/NumberInput.js +147 -0
- package/components/Pagination/Pagination.js +1 -0
- package/components/PinInput/PinInput.css +92 -0
- package/components/PinInput/PinInput.d.ts +46 -0
- package/components/PinInput/PinInput.js +161 -0
- package/components/Popover/Popover.css +5 -6
- package/components/Popover/Popover.js +1 -0
- package/components/RadioButton/RadioButton.js +1 -0
- package/components/Reasoning/Reasoning.js +1 -0
- package/components/SegmentedControl/SegmentedControl.js +1 -0
- package/components/SelectionCard/SelectionCard.js +1 -0
- package/components/ShaderField/ShaderField.js +1 -0
- package/components/ShaderField/field.glsl.d.ts +11 -5
- package/components/ShaderField/field.glsl.js +4 -3
- package/components/ShaderField/useShaderField.d.ts +19 -2
- package/components/ShaderField/useShaderField.js +18 -3
- package/components/Slider/Slider.js +1 -0
- package/components/Sparkline/Sparkline.css +52 -0
- package/components/Sparkline/Sparkline.d.ts +49 -0
- package/components/Sparkline/Sparkline.js +86 -0
- package/components/Stepper/Stepper.css +207 -0
- package/components/Stepper/Stepper.d.ts +41 -0
- package/components/Stepper/Stepper.js +61 -0
- package/components/Tabs/Tabs.js +1 -0
- package/components/TagInput/TagInput.css +160 -0
- package/components/TagInput/TagInput.d.ts +43 -0
- package/components/TagInput/TagInput.js +137 -0
- package/components/Textarea/Textarea.js +1 -0
- package/components/TimePicker/TimePicker.css +230 -0
- package/components/TimePicker/TimePicker.d.ts +46 -0
- package/components/TimePicker/TimePicker.js +246 -0
- package/components/Timeline/Timeline.css +17 -0
- package/components/Timeline/Timeline.d.ts +2 -0
- package/components/Timeline/Timeline.js +1 -0
- package/components/Toast/Toast.js +1 -0
- package/components/ToggleGroup/ToggleGroup.js +1 -0
- package/components/ToggleSwitch/ToggleSwitch.js +1 -0
- package/components/ToolCall/ToolCall.css +7 -11
- package/components/ToolCall/ToolCall.js +1 -0
- package/components/Tooltip/Tooltip.js +1 -0
- package/components/TreeView/TreeView.css +119 -0
- package/components/TreeView/TreeView.d.ts +45 -0
- package/components/TreeView/TreeView.js +232 -0
- package/components/registry.d.ts +22 -6
- package/components/registry.js +3 -1
- package/components/registry.json +246 -20
- package/components/registry.json.d.ts +246 -20
- package/components/registry.json.js +2 -2
- package/index.d.ts +16 -2
- package/index.js +32 -1
- package/package.json +1 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useState, useRef, useId, useMemo, useCallback, useEffect } from "react";
|
|
4
|
+
import { Field } from "../Field/Field.js";
|
|
5
|
+
import "./TimePicker.css";
|
|
6
|
+
import "../../fonts/material-symbols.css";
|
|
7
|
+
function parseMinutes(time) {
|
|
8
|
+
const [h, m] = time.split(":").map(Number);
|
|
9
|
+
return h * 60 + m;
|
|
10
|
+
}
|
|
11
|
+
function toTimeValue(minutes) {
|
|
12
|
+
const h = Math.floor(minutes / 60);
|
|
13
|
+
const m = minutes % 60;
|
|
14
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
15
|
+
}
|
|
16
|
+
function formatTime(time, hourFormat) {
|
|
17
|
+
if (hourFormat === "24") return time;
|
|
18
|
+
const [h, m] = time.split(":").map(Number);
|
|
19
|
+
const suffix = h < 12 ? "AM" : "PM";
|
|
20
|
+
const hour12 = h % 12 === 0 ? 12 : h % 12;
|
|
21
|
+
return `${hour12}:${String(m).padStart(2, "0")} ${suffix}`;
|
|
22
|
+
}
|
|
23
|
+
const TimePicker = React.forwardRef(
|
|
24
|
+
({
|
|
25
|
+
label,
|
|
26
|
+
placeholder = "Select time",
|
|
27
|
+
value,
|
|
28
|
+
defaultValue,
|
|
29
|
+
minTime = "00:00",
|
|
30
|
+
maxTime = "23:30",
|
|
31
|
+
stepMinutes = 30,
|
|
32
|
+
hourFormat = "12",
|
|
33
|
+
size = "default",
|
|
34
|
+
disabled = false,
|
|
35
|
+
required = false,
|
|
36
|
+
error = false,
|
|
37
|
+
helperText,
|
|
38
|
+
onValueChange,
|
|
39
|
+
className = "",
|
|
40
|
+
id,
|
|
41
|
+
...rest
|
|
42
|
+
}, ref) => {
|
|
43
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
44
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
45
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
46
|
+
const rootRef = useRef(null);
|
|
47
|
+
const triggerRef = useRef(null);
|
|
48
|
+
const listRef = useRef(null);
|
|
49
|
+
const isControlled = value !== void 0;
|
|
50
|
+
const selectedValue = isControlled ? value : internalValue;
|
|
51
|
+
const setTriggerRef = (node) => {
|
|
52
|
+
triggerRef.current = node;
|
|
53
|
+
if (typeof ref === "function") ref(node);
|
|
54
|
+
else if (ref) ref.current = node;
|
|
55
|
+
};
|
|
56
|
+
const generatedId = useId();
|
|
57
|
+
const inputId = id || generatedId;
|
|
58
|
+
const baseClass = "ds-timepicker";
|
|
59
|
+
const classes = [
|
|
60
|
+
baseClass,
|
|
61
|
+
size === "compact" ? `${baseClass}--compact` : "",
|
|
62
|
+
isOpen ? `${baseClass}--open` : "",
|
|
63
|
+
error ? `${baseClass}--error` : "",
|
|
64
|
+
disabled ? `${baseClass}--disabled` : "",
|
|
65
|
+
className
|
|
66
|
+
].filter(Boolean).join(" ");
|
|
67
|
+
const timeOptions = useMemo(() => {
|
|
68
|
+
const step = Math.max(1, Math.floor(stepMinutes));
|
|
69
|
+
const start = parseMinutes(minTime);
|
|
70
|
+
const end = parseMinutes(maxTime);
|
|
71
|
+
const opts = [];
|
|
72
|
+
for (let mins = start; mins <= end; mins += step) {
|
|
73
|
+
const v = toTimeValue(mins);
|
|
74
|
+
opts.push({ value: v, label: formatTime(v, hourFormat) });
|
|
75
|
+
}
|
|
76
|
+
return opts;
|
|
77
|
+
}, [minTime, maxTime, stepMinutes, hourFormat]);
|
|
78
|
+
const selectedIndex = timeOptions.findIndex((opt) => opt.value === selectedValue);
|
|
79
|
+
const selectedOption = selectedIndex >= 0 ? timeOptions[selectedIndex] : void 0;
|
|
80
|
+
const openMenu = () => {
|
|
81
|
+
setIsOpen(true);
|
|
82
|
+
setFocusedIndex(selectedIndex >= 0 ? selectedIndex : timeOptions.length > 0 ? 0 : -1);
|
|
83
|
+
};
|
|
84
|
+
const closeMenu = () => {
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
setFocusedIndex(-1);
|
|
87
|
+
};
|
|
88
|
+
const handleToggle = () => {
|
|
89
|
+
if (disabled) return;
|
|
90
|
+
if (isOpen) closeMenu();
|
|
91
|
+
else openMenu();
|
|
92
|
+
};
|
|
93
|
+
const handleSelect = (optionValue) => {
|
|
94
|
+
if (!isControlled) setInternalValue(optionValue);
|
|
95
|
+
onValueChange?.(optionValue);
|
|
96
|
+
closeMenu();
|
|
97
|
+
};
|
|
98
|
+
const handleClickOutside = useCallback((e) => {
|
|
99
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
100
|
+
setIsOpen(false);
|
|
101
|
+
}
|
|
102
|
+
}, []);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
105
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
106
|
+
}, [handleClickOutside]);
|
|
107
|
+
const handleKeyDown = (e) => {
|
|
108
|
+
if (disabled) return;
|
|
109
|
+
switch (e.key) {
|
|
110
|
+
case "Enter":
|
|
111
|
+
case " ":
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
if (isOpen && focusedIndex >= 0 && timeOptions[focusedIndex]) {
|
|
114
|
+
handleSelect(timeOptions[focusedIndex].value);
|
|
115
|
+
} else {
|
|
116
|
+
handleToggle();
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case "ArrowDown":
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
if (!isOpen) {
|
|
122
|
+
openMenu();
|
|
123
|
+
} else {
|
|
124
|
+
setFocusedIndex((prev) => Math.min(prev + 1, timeOptions.length - 1));
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case "ArrowUp":
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
if (!isOpen) {
|
|
130
|
+
openMenu();
|
|
131
|
+
} else {
|
|
132
|
+
setFocusedIndex((prev) => Math.max(prev - 1, 0));
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
case "Home":
|
|
136
|
+
if (isOpen) {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
setFocusedIndex(0);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case "End":
|
|
142
|
+
if (isOpen) {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
setFocusedIndex(timeOptions.length - 1);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case "Escape":
|
|
148
|
+
closeMenu();
|
|
149
|
+
break;
|
|
150
|
+
case "Tab":
|
|
151
|
+
closeMenu();
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (isOpen && focusedIndex >= 0 && listRef.current) {
|
|
157
|
+
const focusedEl = listRef.current.children[focusedIndex];
|
|
158
|
+
focusedEl?.scrollIntoView({ block: "nearest" });
|
|
159
|
+
}
|
|
160
|
+
}, [focusedIndex, isOpen]);
|
|
161
|
+
return /* @__PURE__ */ jsxs(
|
|
162
|
+
Field,
|
|
163
|
+
{
|
|
164
|
+
ref: rootRef,
|
|
165
|
+
className: classes,
|
|
166
|
+
label,
|
|
167
|
+
helperText,
|
|
168
|
+
error,
|
|
169
|
+
required,
|
|
170
|
+
disabled,
|
|
171
|
+
size,
|
|
172
|
+
id: inputId,
|
|
173
|
+
children: [
|
|
174
|
+
/* @__PURE__ */ jsxs(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
...rest,
|
|
178
|
+
type: "button",
|
|
179
|
+
id: inputId,
|
|
180
|
+
ref: setTriggerRef,
|
|
181
|
+
className: `${baseClass}__trigger`,
|
|
182
|
+
"aria-expanded": isOpen,
|
|
183
|
+
"aria-haspopup": "listbox",
|
|
184
|
+
"aria-controls": `${inputId}-listbox`,
|
|
185
|
+
"aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
|
|
186
|
+
"aria-invalid": error || void 0,
|
|
187
|
+
disabled,
|
|
188
|
+
onClick: handleToggle,
|
|
189
|
+
onKeyDown: handleKeyDown,
|
|
190
|
+
children: [
|
|
191
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: "schedule" }),
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
"span",
|
|
194
|
+
{
|
|
195
|
+
className: `${baseClass}__value ${!selectedOption ? `${baseClass}__value--placeholder` : ""}`,
|
|
196
|
+
children: selectedOption ? selectedOption.label : placeholder
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
isOpen && /* @__PURE__ */ jsx(
|
|
204
|
+
"ul",
|
|
205
|
+
{
|
|
206
|
+
className: `${baseClass}__menu`,
|
|
207
|
+
role: "listbox",
|
|
208
|
+
id: `${inputId}-listbox`,
|
|
209
|
+
ref: listRef,
|
|
210
|
+
"aria-labelledby": label ? `${inputId}-label` : void 0,
|
|
211
|
+
children: timeOptions.map((option, index) => /* @__PURE__ */ jsxs(
|
|
212
|
+
"li",
|
|
213
|
+
{
|
|
214
|
+
className: [
|
|
215
|
+
`${baseClass}__option`,
|
|
216
|
+
option.value === selectedValue ? `${baseClass}__option--selected` : "",
|
|
217
|
+
index === focusedIndex ? `${baseClass}__option--focused` : ""
|
|
218
|
+
].filter(Boolean).join(" "),
|
|
219
|
+
role: "option",
|
|
220
|
+
"aria-selected": option.value === selectedValue,
|
|
221
|
+
onClick: () => handleSelect(option.value),
|
|
222
|
+
children: [
|
|
223
|
+
option.label,
|
|
224
|
+
option.value === selectedValue && /* @__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
|
+
);
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
TimePicker.displayName = "TimePicker";
|
|
244
|
+
export {
|
|
245
|
+
TimePicker
|
|
246
|
+
};
|
|
@@ -286,6 +286,23 @@
|
|
|
286
286
|
color: var(--color-status-positive-text);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
.ds-timeline__role-subtitle {
|
|
290
|
+
margin: 0;
|
|
291
|
+
font-family: var(--font-paragraph-family);
|
|
292
|
+
font-size: var(--font-paragraph-size);
|
|
293
|
+
font-weight: var(--font-paragraph-weight);
|
|
294
|
+
line-height: var(--font-paragraph-line-height);
|
|
295
|
+
letter-spacing: var(--font-paragraph-letter-spacing);
|
|
296
|
+
color: var(--color-text-secondary);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* The title and its subtitle read as one header block, so whatever follows
|
|
300
|
+
needs a wider break than the role's own 8px rhythm to separate from it. */
|
|
301
|
+
.ds-timeline__role-subtitle + .ds-timeline__role-description,
|
|
302
|
+
.ds-timeline__role-subtitle + .ds-timeline__role-bullets {
|
|
303
|
+
margin-top: var(--gap-sm);
|
|
304
|
+
}
|
|
305
|
+
|
|
289
306
|
.ds-timeline__role-description {
|
|
290
307
|
margin: 0;
|
|
291
308
|
font-family: var(--font-paragraph-family);
|
|
@@ -13,6 +13,8 @@ export interface TimelineItem {
|
|
|
13
13
|
export interface TimelineRole {
|
|
14
14
|
/** Role or position title */
|
|
15
15
|
title: string;
|
|
16
|
+
/** Secondary line under the title, e.g. the team, org, or product the role sat in */
|
|
17
|
+
subtitle?: React.ReactNode;
|
|
16
18
|
/** Start of the date range, e.g. "May 2024" (omit for entries with no dates) */
|
|
17
19
|
start?: string;
|
|
18
20
|
/** End of the date range, e.g. "Jan 2026" (ignored when `present` is set) */
|
|
@@ -23,6 +23,7 @@ const CompanyTimeline = ({ items, className = "" }) => {
|
|
|
23
23
|
/* @__PURE__ */ jsx("h3", { className: `${BASE_CLASS}__role-title`, children: role.title }),
|
|
24
24
|
renderRoleDates(role)
|
|
25
25
|
] }),
|
|
26
|
+
role.subtitle && /* @__PURE__ */ jsx("p", { className: `${BASE_CLASS}__role-subtitle`, children: role.subtitle }),
|
|
26
27
|
role.description && /* @__PURE__ */ jsx("p", { className: `${BASE_CLASS}__role-description`, children: role.description }),
|
|
27
28
|
role.bullets && role.bullets.length > 0 && /* @__PURE__ */ jsx("ul", { className: `${BASE_CLASS}__role-bullets`, children: role.bullets.map((bullet, bulletIndex) => /* @__PURE__ */ jsx("li", { children: bullet }, bulletIndex)) })
|
|
28
29
|
] }, `${role.title}-${roleIndex}`)) })
|
|
@@ -7,20 +7,19 @@
|
|
|
7
7
|
The container stays neutral — colour lives in
|
|
8
8
|
the indicator and the status word, so a long
|
|
9
9
|
run of calls reads as a list rather than a
|
|
10
|
-
wall of tinted cards. The
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
wall of tinted cards. The border is the
|
|
11
|
+
standard container hairline in every status;
|
|
12
|
+
the status word and icon carry the state.
|
|
13
13
|
============================================ */
|
|
14
14
|
|
|
15
15
|
.ds-tool-call {
|
|
16
16
|
--ds-tool-call-color: var(--color-text-secondary);
|
|
17
|
-
--ds-tool-call-border: var(--color-bg-container-border);
|
|
18
17
|
|
|
19
18
|
display: flex;
|
|
20
19
|
flex-direction: column;
|
|
21
20
|
width: 100%;
|
|
22
21
|
background-color: var(--color-bg-container-primary);
|
|
23
|
-
border: var(--border-xs) solid var(--
|
|
22
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
24
23
|
border-radius: var(--radius-md);
|
|
25
24
|
overflow: hidden;
|
|
26
25
|
}
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
align-items: center;
|
|
35
34
|
gap: var(--gap-sm);
|
|
36
35
|
width: 100%;
|
|
37
|
-
padding: var(--padding-
|
|
36
|
+
padding: var(--padding-sm) var(--padding-md);
|
|
38
37
|
background: none;
|
|
39
38
|
border: none;
|
|
40
39
|
text-align: left;
|
|
@@ -144,7 +143,6 @@
|
|
|
144
143
|
|
|
145
144
|
.ds-tool-call--pending {
|
|
146
145
|
--ds-tool-call-color: var(--color-status-warning-text);
|
|
147
|
-
--ds-tool-call-border: var(--color-status-warning-border);
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
.ds-tool-call--running {
|
|
@@ -157,7 +155,6 @@
|
|
|
157
155
|
|
|
158
156
|
.ds-tool-call--error {
|
|
159
157
|
--ds-tool-call-color: var(--color-status-error-text);
|
|
160
|
-
--ds-tool-call-border: var(--color-status-error-border);
|
|
161
158
|
}
|
|
162
159
|
|
|
163
160
|
/* ============================================
|
|
@@ -184,7 +181,7 @@
|
|
|
184
181
|
}
|
|
185
182
|
|
|
186
183
|
.ds-tool-call--open .ds-tool-call__content {
|
|
187
|
-
padding: var(--padding-
|
|
184
|
+
padding: var(--padding-md);
|
|
188
185
|
border-top: var(--border-xs) solid var(--color-divider);
|
|
189
186
|
}
|
|
190
187
|
|
|
@@ -207,7 +204,6 @@
|
|
|
207
204
|
align-items: center;
|
|
208
205
|
justify-content: flex-end;
|
|
209
206
|
gap: var(--gap-sm);
|
|
210
|
-
padding: var(--padding-sm) var(--padding-
|
|
207
|
+
padding: var(--padding-sm-md) var(--padding-md);
|
|
211
208
|
border-top: var(--border-xs) solid var(--color-divider);
|
|
212
|
-
background-color: var(--color-bg-container-secondary);
|
|
213
209
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
TREEVIEW COMPONENT
|
|
3
|
+
Collapsible hierarchy for files, folders,
|
|
4
|
+
and nested structures
|
|
5
|
+
============================================ */
|
|
6
|
+
|
|
7
|
+
/* Base */
|
|
8
|
+
|
|
9
|
+
.ds-treeview {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
width: 100%;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
list-style: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ============================================
|
|
19
|
+
ITEM — the focusable treeitem (roving tabindex)
|
|
20
|
+
============================================ */
|
|
21
|
+
|
|
22
|
+
.ds-treeview__item {
|
|
23
|
+
outline: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* The focus ring draws on the item's own row, never on the subtree */
|
|
27
|
+
.ds-treeview__item:focus-visible > .ds-treeview__row {
|
|
28
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
29
|
+
outline-offset: -2px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ============================================
|
|
33
|
+
ROW — one line of the tree
|
|
34
|
+
Indentation steps per depth via a custom
|
|
35
|
+
property the component sets inline.
|
|
36
|
+
============================================ */
|
|
37
|
+
|
|
38
|
+
.ds-treeview__row {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--gap-xs);
|
|
42
|
+
padding: var(--padding-xxs) var(--padding-sm);
|
|
43
|
+
padding-left: calc(var(--padding-sm) + var(--ds-treeview-depth, 0) * var(--gap-md));
|
|
44
|
+
border-radius: var(--radius-sm);
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* States */
|
|
50
|
+
|
|
51
|
+
.ds-treeview__row:hover {
|
|
52
|
+
background-color: var(--color-action-passive-bg-hover);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ds-treeview__row--selected {
|
|
56
|
+
background-color: var(--color-action-passive-bg-active);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ds-treeview__row--selected .ds-treeview__label {
|
|
60
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ============================================
|
|
64
|
+
CHEVRON — rotates a quarter turn on expand
|
|
65
|
+
============================================ */
|
|
66
|
+
|
|
67
|
+
.ds-treeview__chevron {
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
--icon-size: var(--icon-size-sm);
|
|
70
|
+
color: var(--color-icon-primary);
|
|
71
|
+
transition: transform var(--motion-duration-fast) var(--motion-ease-standard);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ds-treeview__item[aria-expanded='true'] > .ds-treeview__row .ds-treeview__chevron {
|
|
75
|
+
transform: rotate(90deg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Leaves keep the chevron column so labels align across depths */
|
|
79
|
+
.ds-treeview__chevron--spacer {
|
|
80
|
+
display: inline-block;
|
|
81
|
+
width: var(--icon-size-sm);
|
|
82
|
+
height: var(--icon-size-sm);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ============================================
|
|
86
|
+
ICON
|
|
87
|
+
============================================ */
|
|
88
|
+
|
|
89
|
+
.ds-treeview__icon {
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
--icon-size: var(--icon-size-sm);
|
|
92
|
+
color: var(--color-icon-primary);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ============================================
|
|
96
|
+
LABEL
|
|
97
|
+
============================================ */
|
|
98
|
+
|
|
99
|
+
.ds-treeview__label {
|
|
100
|
+
font-family: var(--font-paragraph-sm-family);
|
|
101
|
+
font-size: var(--font-paragraph-sm-size);
|
|
102
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
103
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
104
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
105
|
+
color: var(--color-text-primary);
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
white-space: nowrap;
|
|
108
|
+
text-overflow: ellipsis;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ============================================
|
|
112
|
+
GROUP — nested children
|
|
113
|
+
============================================ */
|
|
114
|
+
|
|
115
|
+
.ds-treeview__group {
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding: 0;
|
|
118
|
+
list-style: none;
|
|
119
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface TreeViewNode {
|
|
3
|
+
/** Unique identifier for the node */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Text shown on the row */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Material Symbol name; defaults to `folder` for branches and `draft` for leaves */
|
|
8
|
+
icon?: string;
|
|
9
|
+
/** Child nodes; a non-empty list makes the node an expandable branch */
|
|
10
|
+
children?: TreeViewNode[];
|
|
11
|
+
}
|
|
12
|
+
/** Props owned by TreeView itself — everything else falls through to the root <ul>. */
|
|
13
|
+
type TreeViewOwnProps = {
|
|
14
|
+
/** Tree data to render */
|
|
15
|
+
nodes: TreeViewNode[];
|
|
16
|
+
/** IDs of initially expanded branches (uncontrolled) */
|
|
17
|
+
defaultExpandedIds?: string[];
|
|
18
|
+
/** Expanded branch IDs (controlled) — pair with `onExpandedChange` */
|
|
19
|
+
expandedIds?: string[];
|
|
20
|
+
/** Called with the full list of expanded branch IDs whenever a branch toggles */
|
|
21
|
+
onExpandedChange?: (ids: string[]) => void;
|
|
22
|
+
/** Selected node ID (controlled) — pair with `onSelect` */
|
|
23
|
+
selectedId?: string;
|
|
24
|
+
/** ID of the initially selected node (uncontrolled) */
|
|
25
|
+
defaultSelectedId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Called with the node's ID when a row is selected via click, Enter, or
|
|
28
|
+
* Space. Intentionally shadows the native `select` event handler, which
|
|
29
|
+
* has no meaning on a tree widget.
|
|
30
|
+
*/
|
|
31
|
+
onSelect?: (id: string) => void;
|
|
32
|
+
/** Additional CSS classes */
|
|
33
|
+
className?: string;
|
|
34
|
+
};
|
|
35
|
+
export interface TreeViewProps extends TreeViewOwnProps, Omit<React.ComponentPropsWithoutRef<'ul'>, keyof TreeViewOwnProps> {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* TreeView renders a collapsible hierarchy — files, folders, and other
|
|
39
|
+
* nested structures — as a WAI-ARIA tree. Branches expand and collapse,
|
|
40
|
+
* one node can be selected, and a roving tabindex gives the whole tree a
|
|
41
|
+
* single tab stop with full arrow-key navigation. Expansion and selection
|
|
42
|
+
* each work controlled or uncontrolled.
|
|
43
|
+
*/
|
|
44
|
+
export declare const TreeView: React.ForwardRefExoticComponent<TreeViewProps & React.RefAttributes<HTMLUListElement>>;
|
|
45
|
+
export {};
|