@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.
Files changed (109) hide show
  1. package/README.md +4 -4
  2. package/components/Accordion/Accordion.js +1 -0
  3. package/components/AgentPlan/AgentPlan.css +231 -0
  4. package/components/AgentPlan/AgentPlan.d.ts +41 -0
  5. package/components/AgentPlan/AgentPlan.js +100 -0
  6. package/components/AgentStatus/AgentStatus.js +1 -0
  7. package/components/AiButton/AiButton.js +1 -0
  8. package/components/AlertDialog/AlertDialog.js +1 -0
  9. package/components/AnchorNav/AnchorNav.css +63 -0
  10. package/components/AnchorNav/AnchorNav.d.ts +33 -0
  11. package/components/AnchorNav/AnchorNav.js +82 -0
  12. package/components/AppLayout/AppLayout.js +1 -0
  13. package/components/AppSidebar/AppSidebar.js +1 -0
  14. package/components/Avatar/Avatar.js +1 -0
  15. package/components/Button/Button.js +1 -0
  16. package/components/Card/Card.js +1 -0
  17. package/components/Carousel/Carousel.js +1 -0
  18. package/components/ChatThread/ChatThread.js +1 -0
  19. package/components/Checkbox/Checkbox.js +1 -0
  20. package/components/CodeBlock/CodeBlock.js +1 -0
  21. package/components/CodeDiff/CodeDiff.css +175 -0
  22. package/components/CodeDiff/CodeDiff.d.ts +58 -0
  23. package/components/CodeDiff/CodeDiff.js +114 -0
  24. package/components/ColorPicker/ColorPicker.js +1 -0
  25. package/components/Combobox/Combobox.js +1 -0
  26. package/components/CommandPalette/CommandPalette.js +1 -0
  27. package/components/Composer/Composer.js +1 -0
  28. package/components/ContactCard/ContactCard.js +1 -0
  29. package/components/ContextMenu/ContextMenu.js +1 -0
  30. package/components/DataTable/DataTable.css +105 -0
  31. package/components/DataTable/DataTable.d.ts +81 -0
  32. package/components/DataTable/DataTable.js +236 -0
  33. package/components/DateInput/DateInput.js +1 -0
  34. package/components/DatePicker/DatePicker.js +1 -0
  35. package/components/Dialog/Dialog.js +1 -0
  36. package/components/Drawer/Drawer.js +1 -0
  37. package/components/Dropdown/Dropdown.js +1 -0
  38. package/components/DropdownMenu/DropdownMenu.css +1 -1
  39. package/components/DropdownMenu/DropdownMenu.js +1 -0
  40. package/components/EventCalendar/EventCalendar.css +328 -0
  41. package/components/EventCalendar/EventCalendar.d.ts +51 -0
  42. package/components/EventCalendar/EventCalendar.js +214 -0
  43. package/components/Field/Field.js +1 -0
  44. package/components/Field/FieldContext.js +1 -0
  45. package/components/FileInput/FileInput.js +1 -0
  46. package/components/Input/Input.js +1 -0
  47. package/components/InterruptCard/InterruptCard.css +11 -21
  48. package/components/InterruptCard/InterruptCard.d.ts +5 -6
  49. package/components/MessageCard/MessageCard.css +1 -2
  50. package/components/ModelPicker/ModelPicker.css +240 -0
  51. package/components/ModelPicker/ModelPicker.d.ts +60 -0
  52. package/components/ModelPicker/ModelPicker.js +218 -0
  53. package/components/NavList/NavList.js +1 -0
  54. package/components/NotificationCenter/NotificationCenter.css +262 -0
  55. package/components/NotificationCenter/NotificationCenter.d.ts +72 -0
  56. package/components/NotificationCenter/NotificationCenter.js +129 -0
  57. package/components/NumberInput/NumberInput.css +154 -0
  58. package/components/NumberInput/NumberInput.d.ts +44 -0
  59. package/components/NumberInput/NumberInput.js +147 -0
  60. package/components/Pagination/Pagination.js +1 -0
  61. package/components/PinInput/PinInput.css +92 -0
  62. package/components/PinInput/PinInput.d.ts +46 -0
  63. package/components/PinInput/PinInput.js +161 -0
  64. package/components/Popover/Popover.css +5 -6
  65. package/components/Popover/Popover.js +1 -0
  66. package/components/RadioButton/RadioButton.js +1 -0
  67. package/components/Reasoning/Reasoning.js +1 -0
  68. package/components/SegmentedControl/SegmentedControl.js +1 -0
  69. package/components/SelectionCard/SelectionCard.js +1 -0
  70. package/components/ShaderField/ShaderField.js +1 -0
  71. package/components/ShaderField/field.glsl.d.ts +11 -5
  72. package/components/ShaderField/field.glsl.js +4 -3
  73. package/components/ShaderField/useShaderField.d.ts +19 -2
  74. package/components/ShaderField/useShaderField.js +18 -3
  75. package/components/Slider/Slider.js +1 -0
  76. package/components/Sparkline/Sparkline.css +52 -0
  77. package/components/Sparkline/Sparkline.d.ts +49 -0
  78. package/components/Sparkline/Sparkline.js +86 -0
  79. package/components/Stepper/Stepper.css +207 -0
  80. package/components/Stepper/Stepper.d.ts +41 -0
  81. package/components/Stepper/Stepper.js +61 -0
  82. package/components/Tabs/Tabs.js +1 -0
  83. package/components/TagInput/TagInput.css +160 -0
  84. package/components/TagInput/TagInput.d.ts +43 -0
  85. package/components/TagInput/TagInput.js +137 -0
  86. package/components/Textarea/Textarea.js +1 -0
  87. package/components/TimePicker/TimePicker.css +230 -0
  88. package/components/TimePicker/TimePicker.d.ts +46 -0
  89. package/components/TimePicker/TimePicker.js +246 -0
  90. package/components/Timeline/Timeline.css +17 -0
  91. package/components/Timeline/Timeline.d.ts +2 -0
  92. package/components/Timeline/Timeline.js +1 -0
  93. package/components/Toast/Toast.js +1 -0
  94. package/components/ToggleGroup/ToggleGroup.js +1 -0
  95. package/components/ToggleSwitch/ToggleSwitch.js +1 -0
  96. package/components/ToolCall/ToolCall.css +7 -11
  97. package/components/ToolCall/ToolCall.js +1 -0
  98. package/components/Tooltip/Tooltip.js +1 -0
  99. package/components/TreeView/TreeView.css +119 -0
  100. package/components/TreeView/TreeView.d.ts +45 -0
  101. package/components/TreeView/TreeView.js +232 -0
  102. package/components/registry.d.ts +22 -6
  103. package/components/registry.js +3 -1
  104. package/components/registry.json +246 -20
  105. package/components/registry.json.d.ts +246 -20
  106. package/components/registry.json.js +2 -2
  107. package/index.d.ts +16 -2
  108. package/index.js +32 -1
  109. 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}`)) })
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsxs, jsx } from "react/jsx-runtime";
2
3
  import { useState, useSyncExternalStore, useCallback, useContext, createContext, useRef, useEffect } from "react";
3
4
  import ReactDOM from "react-dom";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx } from "react/jsx-runtime";
2
3
  import React from "react";
3
4
  import "./ToggleGroup.css";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsxs, jsx } from "react/jsx-runtime";
2
3
  import React from "react";
3
4
  import "./ToggleSwitch.css";
@@ -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 two states a person
11
- has to act on, pending and error, also take a
12
- coloured border.
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(--ds-tool-call-border);
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-xs) var(--padding-sm-md);
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-sm-md);
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-sm-md);
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
  }
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
3
  import React, { useId, useState, useCallback } from "react";
3
4
  import { Spinner } from "../Spinner/Spinner.js";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsxs, jsx } from "react/jsx-runtime";
2
3
  import { useState, useRef, useId, isValidElement, cloneElement } from "react";
3
4
  import "./Tooltip.css";
@@ -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 {};