@api7/ui-components 0.6.0 → 0.8.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 (75) hide show
  1. package/README.md +170 -9
  2. package/dist/components/anchor/anchor.d.ts +28 -0
  3. package/dist/components/anchor/anchor.js +249 -0
  4. package/dist/components/breadcrumb/breadcrumb.d.ts +15 -0
  5. package/dist/components/breadcrumb/breadcrumb.js +60 -0
  6. package/dist/components/button/button.d.ts +1 -1
  7. package/dist/components/button/button.js +1 -1
  8. package/dist/components/calendar/calendar.js +24 -3
  9. package/dist/components/cascader/cascader-data.d.ts +26 -0
  10. package/dist/components/cascader/cascader-data.js +110 -0
  11. package/dist/components/cascader/cascader.d.ts +41 -0
  12. package/dist/components/cascader/cascader.js +513 -0
  13. package/dist/components/code/code.d.ts +9 -0
  14. package/dist/components/code/code.js +20 -0
  15. package/dist/components/code/copy-button.js +42 -0
  16. package/dist/components/code-block/code-block.d.ts +16 -0
  17. package/dist/components/code-block/code-block.js +45 -0
  18. package/dist/components/code-block/highlight.js +67 -0
  19. package/dist/components/collapsible/collapsible.d.ts +14 -0
  20. package/dist/components/collapsible/collapsible.js +36 -0
  21. package/dist/components/combobox/combobox-loading.d.ts +18 -0
  22. package/dist/components/combobox/combobox-loading.js +148 -0
  23. package/dist/components/combobox/combobox.d.ts +13 -6
  24. package/dist/components/combobox/combobox.js +58 -10
  25. package/dist/components/data-table/data-table.d.ts +27 -3
  26. package/dist/components/data-table/data-table.js +155 -40
  27. package/dist/components/data-table/expandable-row.js +40 -0
  28. package/dist/components/data-table/filter-header.d.ts +28 -5
  29. package/dist/components/data-table/filter-header.js +103 -40
  30. package/dist/components/data-table/index.d.ts +3 -3
  31. package/dist/components/data-table/use-table-layout.js +84 -0
  32. package/dist/components/date-picker/date-picker.js +11 -2
  33. package/dist/components/description-list/description-list.d.ts +1 -1
  34. package/dist/components/dialog/dialog.js +1 -1
  35. package/dist/components/drawer/drawer.js +1 -1
  36. package/dist/components/empty/empty.d.ts +1 -1
  37. package/dist/components/field/labeled-field.js +1 -1
  38. package/dist/components/file-upload/file-upload.d.ts +44 -0
  39. package/dist/components/file-upload/file-upload.js +195 -0
  40. package/dist/components/form/choice-fields.js +2 -2
  41. package/dist/components/form/date-fields.d.ts +1 -1
  42. package/dist/components/form/date-fields.js +1 -1
  43. package/dist/components/form/form-item.js +1 -1
  44. package/dist/components/form/form.js +1 -1
  45. package/dist/components/form/index.d.ts +2 -2
  46. package/dist/components/form/index.js +4 -4
  47. package/dist/components/form/text-field.js +2 -2
  48. package/dist/components/form/toggle-fields.d.ts +1 -1
  49. package/dist/components/form/toggle-fields.js +3 -3
  50. package/dist/components/pagination/pagination.d.ts +20 -4
  51. package/dist/components/pagination/pagination.js +83 -32
  52. package/dist/components/relative-date-range-picker/relative-date-range-picker.d.ts +14 -0
  53. package/dist/components/relative-date-range-picker/relative-date-range-picker.js +245 -0
  54. package/dist/components/relative-date-range-picker/relative-date-range.d.ts +26 -0
  55. package/dist/components/relative-date-range-picker/relative-date-range.js +109 -0
  56. package/dist/components/select/select.js +1 -1
  57. package/dist/components/sidebar/sidebar.js +2 -2
  58. package/dist/components/steps/steps.d.ts +23 -0
  59. package/dist/components/steps/steps.js +86 -0
  60. package/dist/components/table/table.d.ts +2 -1
  61. package/dist/components/table/table.js +2 -1
  62. package/dist/components/toast/toast.d.ts +1 -1
  63. package/dist/components/toast/toast.js +1 -1
  64. package/dist/config/config-provider.d.ts +2 -2
  65. package/dist/config/messages.d.ts +115 -1
  66. package/dist/config/messages.js +140 -2
  67. package/dist/hooks/use-clipboard.d.ts +11 -0
  68. package/dist/hooks/use-clipboard.js +75 -0
  69. package/dist/hooks/use-field-label-with-text.js +32 -0
  70. package/dist/index.d.ts +26 -13
  71. package/dist/index.js +15 -4
  72. package/dist/theme.css +1 -0
  73. package/dist/tokens/index.d.ts +1 -1
  74. package/dist/tokens/index.js +1 -1
  75. package/package.json +7 -6
@@ -0,0 +1,513 @@
1
+ "use client";
2
+ import { isBranch, pathKey, useCascaderData } from "./cascader-data.js";
3
+ import { Popover as Popover$1, PopoverContent } from "../popover/popover.js";
4
+ import { useMessages } from "../../config/config-provider.js";
5
+ import { cn } from "../../lib/cn.js";
6
+ import { controlFocus, controlSurface } from "../../lib/control.js";
7
+ import { listItemClassName } from "../../lib/popup.js";
8
+ import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { CheckIcon, ChevronDownIcon, ChevronRightIcon, XIcon } from "lucide-react";
11
+ import { Popover } from "@base-ui/react/popover";
12
+ import { Select } from "@base-ui/react/select";
13
+ // src/components/cascader/cascader.tsx
14
+ const EMPTY = [];
15
+ function scrollSelectedOptions(lists) {
16
+ for (const list of lists) {
17
+ const selected = list?.querySelector("[aria-selected=\"true\"]");
18
+ const scroller = list?.parentElement;
19
+ if (!selected || !scroller) continue;
20
+ const item = selected.getBoundingClientRect();
21
+ const viewport = scroller.getBoundingClientRect();
22
+ if (item.top < viewport.top) scroller.scrollTop += item.top - viewport.top;
23
+ else if (item.bottom > viewport.bottom) scroller.scrollTop += item.bottom - viewport.bottom;
24
+ }
25
+ }
26
+ function setRef(ref, value) {
27
+ if (typeof ref === "function") {
28
+ const cleanup = ref(value);
29
+ return typeof cleanup === "function" ? cleanup : () => ref(null);
30
+ }
31
+ if (ref) ref.current = value;
32
+ return () => {
33
+ if (ref) ref.current = null;
34
+ };
35
+ }
36
+ function Cascader(props) {
37
+ const { value, defaultValue, onValueChange, ...controlOptions } = props;
38
+ const [internalValue, setInternalValue] = useState(defaultValue ?? null);
39
+ const currentValue = ("value" in props ? value : internalValue) ?? EMPTY;
40
+ const changeValue = (value, options) => {
41
+ if (!("value" in props)) setInternalValue(value);
42
+ onValueChange?.(value, options);
43
+ };
44
+ return /* @__PURE__ */ jsx(Select.Root, {
45
+ id: props.id,
46
+ name: props.name,
47
+ form: props.form,
48
+ disabled: props.disabled,
49
+ readOnly: props.readOnly,
50
+ required: props.required,
51
+ value: currentValue.length ? JSON.stringify(currentValue) : "",
52
+ open: false,
53
+ onOpenChange: (_, details) => details.cancel(),
54
+ children: /* @__PURE__ */ jsx(Select.Trigger, {
55
+ nativeButton: false,
56
+ render: (controlProps, state) => /* @__PURE__ */ jsx(CascaderControl, {
57
+ ...controlOptions,
58
+ disabled: props.disabled || state.disabled,
59
+ currentValue,
60
+ changeValue,
61
+ resetValue: () => {
62
+ if (!("value" in props)) setInternalValue(defaultValue ?? null);
63
+ },
64
+ controlProps: {
65
+ id: controlProps.id,
66
+ ref: controlProps.ref,
67
+ "aria-labelledby": controlProps["aria-labelledby"],
68
+ "aria-describedby": controlProps["aria-describedby"],
69
+ "aria-invalid": controlProps["aria-invalid"],
70
+ "aria-required": controlProps["aria-required"],
71
+ onFocus: controlProps.onFocus,
72
+ onBlur: controlProps.onBlur
73
+ }
74
+ })
75
+ })
76
+ });
77
+ }
78
+ function CascaderControl({ options, loadChildren, resetKey, currentValue, changeValue, resetValue, controlProps, changeOnSelect = false, searchable = false, loading = false, clearable = false, disabled = false, readOnly = false, placeholder, open: controlledOpen, defaultOpen = false, onOpenChange, displayValue, popupProps, className, ref: externalRef, form, ...inputProps }) {
79
+ const messages = useMessages().cascader;
80
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
81
+ const open = !disabled && !readOnly && (controlledOpen ?? internalOpen);
82
+ const [expanded, setExpanded] = useState(currentValue);
83
+ const [query, setQuery] = useState("");
84
+ const [active, setActive] = useState({});
85
+ const listRefs = useRef([]);
86
+ const columnsRef = useRef(null);
87
+ const pendingFocus = useRef(null);
88
+ const typeahead = useRef({
89
+ text: "",
90
+ time: 0
91
+ });
92
+ const id = useId();
93
+ const triggerRef = useRef(null);
94
+ const labelledBy = inputProps["aria-labelledby"] ?? controlProps["aria-labelledby"];
95
+ const triggerRefCallback = useMemo(() => (node) => {
96
+ triggerRef.current = node;
97
+ if (!node) return;
98
+ const cleanups = [setRef(controlProps.ref, node), setRef(externalRef, node)];
99
+ return () => {
100
+ triggerRef.current = null;
101
+ cleanups.forEach((cleanup) => cleanup());
102
+ };
103
+ }, [
104
+ controlProps.ref,
105
+ externalRef,
106
+ triggerRef
107
+ ]);
108
+ const data = useCascaderData(options, loadChildren, resetKey, !disabled && !readOnly && (open || currentValue.length > 1));
109
+ const selectedOptions = data.resolve(currentValue);
110
+ const selectedKey = pathKey(currentValue);
111
+ const optionsKey = data.optionsKey;
112
+ const [previous, setPrevious] = useState({
113
+ selectedKey,
114
+ optionsKey,
115
+ resetKey,
116
+ open
117
+ });
118
+ if (previous.selectedKey !== selectedKey || previous.optionsKey !== optionsKey || !Object.is(previous.resetKey, resetKey) || previous.open !== open) {
119
+ setPrevious({
120
+ selectedKey,
121
+ optionsKey,
122
+ resetKey,
123
+ open
124
+ });
125
+ setExpanded(currentValue);
126
+ setActive({});
127
+ if (previous.open !== open && !open) setQuery("");
128
+ }
129
+ const resetRef = useRef(resetValue);
130
+ useLayoutEffect(() => {
131
+ resetRef.current = resetValue;
132
+ }, [resetValue]);
133
+ useEffect(() => {
134
+ const element = triggerRef.current?.form;
135
+ let timer;
136
+ const reset = (event) => {
137
+ timer = setTimeout(() => {
138
+ if (!event.defaultPrevented) resetRef.current();
139
+ }, 0);
140
+ };
141
+ element?.addEventListener("reset", reset);
142
+ return () => {
143
+ element?.removeEventListener("reset", reset);
144
+ clearTimeout(timer);
145
+ };
146
+ }, [form, triggerRef]);
147
+ useEffect(() => {
148
+ if (disabled || readOnly || loading || !loadChildren) return;
149
+ for (const path of [currentValue, ...open ? [expanded] : []]) {
150
+ const resolved = data.resolve(path);
151
+ const last = resolved.at(-1);
152
+ if (!last || last.disabled || !isBranch(last)) continue;
153
+ const values = resolved.map((item) => item.value);
154
+ if ((resolved.length < path.length || open && path === expanded) && !data.children(last, values) && data.entries.get(pathKey(values))?.status !== "error") data.load(resolved);
155
+ }
156
+ }, [
157
+ data,
158
+ currentValue,
159
+ expanded,
160
+ open,
161
+ disabled,
162
+ readOnly,
163
+ loading,
164
+ loadChildren
165
+ ]);
166
+ const changeOpen = (next) => {
167
+ if (next && (disabled || readOnly)) return;
168
+ if (controlledOpen === void 0) setInternalOpen(next);
169
+ onOpenChange?.(next);
170
+ };
171
+ const choose = (path, commit) => {
172
+ if (disabled || readOnly || path.some((item) => item.disabled)) return;
173
+ const values = path.map((item) => item.value);
174
+ const last = path.at(-1);
175
+ setExpanded(values);
176
+ setActive((prev) => Object.fromEntries(Object.entries(prev).filter(([level]) => Number(level) < values.length)));
177
+ if (commit && (changeOnSelect || !isBranch(last))) {
178
+ changeValue(values, path);
179
+ if (!isBranch(last) || query.trim()) changeOpen(false);
180
+ }
181
+ };
182
+ const columns = [{
183
+ parent: [],
184
+ items: loading ? [] : options
185
+ }];
186
+ let parent = [];
187
+ for (const value of expanded) {
188
+ const option = columns.at(-1).items.find((item) => item.value === value);
189
+ if (!option || option.disabled || !isBranch(option)) break;
190
+ parent = [...parent, option];
191
+ columns.push({
192
+ parent,
193
+ items: data.children(option, parent.map((item) => item.value)) ?? []
194
+ });
195
+ }
196
+ const searchPaths = [];
197
+ const search = query.trim().toLowerCase();
198
+ const collect = (items, ancestors) => {
199
+ for (const item of items) {
200
+ if (item.disabled) continue;
201
+ const path = [...ancestors, item];
202
+ if ((changeOnSelect || !isBranch(item)) && path.some((part) => part.label.toLowerCase().includes(search))) searchPaths.push(path);
203
+ const children = data.children(item, path.map((part) => part.value));
204
+ if (children) collect(children, path);
205
+ }
206
+ };
207
+ if (search) collect(loading ? [] : options, []);
208
+ const visibleColumns = search ? [{
209
+ parent: [],
210
+ items: searchPaths.map((path) => path.at(-1))
211
+ }] : columns;
212
+ const columnCount = visibleColumns.length;
213
+ useLayoutEffect(() => {
214
+ if (!open || search) return;
215
+ const revealLastColumn = () => {
216
+ const scroller = columnsRef.current;
217
+ if (scroller) scroller.scrollLeft = getComputedStyle(scroller).direction === "rtl" ? -scroller.scrollWidth : scroller.scrollWidth;
218
+ };
219
+ revealLastColumn();
220
+ const frame = requestAnimationFrame(revealLastColumn);
221
+ return () => cancelAnimationFrame(frame);
222
+ }, [
223
+ open,
224
+ search,
225
+ columnCount
226
+ ]);
227
+ useLayoutEffect(() => {
228
+ if (open && !search) scrollSelectedOptions(listRefs.current.slice(0, columnCount));
229
+ }, [
230
+ open,
231
+ search,
232
+ columnCount,
233
+ selectedKey,
234
+ data.entries,
235
+ options
236
+ ]);
237
+ const rowIndex = (items, level) => {
238
+ const index = active[level];
239
+ const expandedIndex = search ? -1 : items.findIndex((item) => !item.disabled && item.value === expanded[level]);
240
+ return index !== void 0 && items[index] && !items[index].disabled ? index : expandedIndex >= 0 ? expandedIndex : items.findIndex((item) => !item.disabled);
241
+ };
242
+ useLayoutEffect(() => {
243
+ if (pendingFocus.current !== null) {
244
+ listRefs.current[pendingFocus.current]?.focus();
245
+ pendingFocus.current = null;
246
+ }
247
+ for (const [level, index] of Object.entries(active)) if (document.activeElement === listRefs.current[Number(level)]) document.getElementById(`${id}-${level}-${index}`)?.scrollIntoView({
248
+ block: "nearest",
249
+ inline: "nearest"
250
+ });
251
+ });
252
+ const handleKey = (event, column, level) => {
253
+ event.currentTarget.focus();
254
+ const { items } = column;
255
+ const index = rowIndex(items, level);
256
+ const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
257
+ const forward = rtl ? "ArrowLeft" : "ArrowRight";
258
+ const back = rtl ? "ArrowRight" : "ArrowLeft";
259
+ let next = index;
260
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
261
+ const step = event.key === "ArrowDown" ? 1 : -1;
262
+ for (let count = 1; count <= items.length; count += 1) {
263
+ const candidate = (index + step * count + items.length) % items.length;
264
+ if (!items[candidate].disabled) {
265
+ next = candidate;
266
+ break;
267
+ }
268
+ }
269
+ } else if (event.key === "Home" || event.key === "End") next = event.key === "Home" ? items.findIndex((item) => !item.disabled) : items.reduce((last, item, position) => item.disabled ? last : position, -1);
270
+ else if (event.key === back && !search) {
271
+ event.preventDefault();
272
+ listRefs.current[level - 1]?.focus();
273
+ return;
274
+ } else if ([
275
+ "Enter",
276
+ " ",
277
+ forward
278
+ ].includes(event.key) && index >= 0) {
279
+ event.preventDefault();
280
+ const path = search ? searchPaths[index] : [...column.parent, items[index]];
281
+ if (event.key === forward && (search || !isBranch(items[index]))) return;
282
+ choose(path, event.key !== forward);
283
+ if (event.key === forward) pendingFocus.current = level + 1;
284
+ return;
285
+ } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
286
+ const now = event.timeStamp;
287
+ typeahead.current = {
288
+ text: (now - typeahead.current.time < 700 ? typeahead.current.text : "") + event.key.toLowerCase(),
289
+ time: now
290
+ };
291
+ const match = items.findIndex((item) => !item.disabled && item.label.toLowerCase().startsWith(typeahead.current.text));
292
+ if (match >= 0) next = match;
293
+ } else return;
294
+ event.preventDefault();
295
+ setActive((prev) => ({
296
+ ...prev,
297
+ [level]: next
298
+ }));
299
+ };
300
+ const label = placeholder ?? messages.placeholder;
301
+ const renderedValue = displayValue?.(currentValue, selectedOptions) ?? currentValue.map((value, index) => selectedOptions[index]?.label ?? String(value)).join(" / ");
302
+ const clear = () => {
303
+ changeValue(null, []);
304
+ setExpanded([]);
305
+ setQuery("");
306
+ triggerRef.current?.focus();
307
+ };
308
+ return /* @__PURE__ */ jsxs(Popover$1, {
309
+ open,
310
+ onOpenChange: changeOpen,
311
+ onOpenChangeComplete: (opened) => {
312
+ if (opened && !search) scrollSelectedOptions(listRefs.current.slice(0, columnCount));
313
+ },
314
+ children: [/* @__PURE__ */ jsxs("div", {
315
+ className: cn("relative w-full", className),
316
+ "data-slot": "cascader",
317
+ children: [
318
+ /* @__PURE__ */ jsx(Popover.Trigger, {
319
+ nativeButton: false,
320
+ role: "combobox",
321
+ disabled,
322
+ "aria-readonly": readOnly || void 0,
323
+ "aria-autocomplete": searchable ? "list" : "none",
324
+ "aria-labelledby": labelledBy,
325
+ "aria-label": inputProps["aria-label"] ?? (labelledBy ? void 0 : label),
326
+ "aria-busy": loading || void 0,
327
+ className: cn(controlSurface, controlFocus, "h-8 truncate ps-3 pe-10 text-start", !searchable && "cursor-pointer", clearable && currentValue.length > 0 && !disabled && !readOnly && "pe-16"),
328
+ "data-slot": "cascader-trigger",
329
+ render: /* @__PURE__ */ jsx("input", {
330
+ ...controlProps,
331
+ ...inputProps,
332
+ ref: triggerRefCallback,
333
+ name: void 0,
334
+ required: false,
335
+ form,
336
+ type: "text",
337
+ readOnly: !searchable || readOnly,
338
+ autoComplete: "off",
339
+ value: searchable && open ? query : renderedValue,
340
+ placeholder: searchable && open && currentValue.length ? renderedValue : label,
341
+ onFocus: (event) => {
342
+ controlProps.onFocus?.(event);
343
+ inputProps.onFocus?.(event);
344
+ if (searchable && !open) event.currentTarget.select();
345
+ },
346
+ onBlur: (event) => {
347
+ controlProps.onBlur?.(event);
348
+ inputProps.onBlur?.(event);
349
+ },
350
+ onChange: (event) => {
351
+ if (!open) changeOpen(true);
352
+ setQuery(event.target.value);
353
+ setActive({});
354
+ }
355
+ }),
356
+ onClick: (event) => {
357
+ event.preventBaseUIHandler();
358
+ if (!event.defaultPrevented && !open) changeOpen(true);
359
+ },
360
+ onKeyDown: (event) => {
361
+ event.preventBaseUIHandler();
362
+ if (event.defaultPrevented || event.nativeEvent.isComposing) return;
363
+ if ([
364
+ "ArrowDown",
365
+ "ArrowUp",
366
+ "Enter"
367
+ ].includes(event.key) || !searchable && event.key === " ") {
368
+ event.preventDefault();
369
+ if (open) listRefs.current[0]?.focus();
370
+ else changeOpen(true);
371
+ }
372
+ },
373
+ onKeyUp: (event) => event.preventBaseUIHandler()
374
+ }),
375
+ /* @__PURE__ */ jsx(ChevronDownIcon, {
376
+ "aria-hidden": true,
377
+ className: "pointer-events-none absolute end-3 top-1/2 size-5 -translate-y-1/2"
378
+ }),
379
+ clearable && currentValue.length > 0 && !disabled && !readOnly && /* @__PURE__ */ jsx("button", {
380
+ type: "button",
381
+ "aria-label": messages.clear,
382
+ className: cn("absolute end-10 top-1/2 -translate-y-1/2 rounded-sm text-muted-foreground hover:text-foreground", "focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 aria-invalid:focus-visible:ring-destructive/40 data-invalid:focus-visible:ring-destructive/40"),
383
+ onClick: clear,
384
+ children: /* @__PURE__ */ jsx(XIcon, {
385
+ "aria-hidden": true,
386
+ className: "size-3"
387
+ })
388
+ })
389
+ ]
390
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
391
+ "aria-label": inputProps["aria-label"] ?? label,
392
+ initialFocus: () => searchable ? triggerRef.current : listRefs.current[0],
393
+ ...popupProps,
394
+ className: cn("w-auto min-w-(--anchor-width) overflow-hidden p-0", popupProps?.className),
395
+ children: [
396
+ searchable && loadChildren && /* @__PURE__ */ jsx("p", {
397
+ className: "border-b border-border p-2 text-xs text-muted-foreground",
398
+ children: messages.searchHint
399
+ }),
400
+ /* @__PURE__ */ jsx("span", {
401
+ id: `${id}-branch`,
402
+ className: "sr-only",
403
+ children: messages.branch
404
+ }),
405
+ /* @__PURE__ */ jsx("div", {
406
+ ref: columnsRef,
407
+ className: "flex max-w-[calc(100vw-2rem)] overflow-x-auto",
408
+ "data-slot": "cascader-columns",
409
+ children: visibleColumns.map((column, level) => {
410
+ const values = column.parent.map((item) => item.value);
411
+ const entry = data.entries.get(pathKey(values));
412
+ const busy = loading || entry?.status === "loading";
413
+ const failed = entry?.status === "error";
414
+ const index = rowIndex(column.items, level);
415
+ return /* @__PURE__ */ jsxs("div", {
416
+ className: "max-h-[300px] min-w-44 flex-1 overflow-y-auto border-e border-border bg-muted last:border-e-0",
417
+ children: [
418
+ /* @__PURE__ */ jsx("div", {
419
+ role: "listbox",
420
+ tabIndex: 0,
421
+ ref: (node) => {
422
+ listRefs.current[level] = node;
423
+ },
424
+ "aria-label": search ? messages.results : messages.level({
425
+ level: level + 1,
426
+ label: column.parent.at(-1)?.label ?? label
427
+ }),
428
+ "aria-activedescendant": index >= 0 ? `${id}-${level}-${index}` : void 0,
429
+ "aria-busy": busy || void 0,
430
+ className: "min-h-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40",
431
+ onKeyDown: (event) => handleKey(event, column, level),
432
+ children: column.items.map((item, itemIndex) => {
433
+ const path = search ? searchPaths[itemIndex] : [...column.parent, item];
434
+ const pathValues = path.map((part) => part.value);
435
+ const selected = pathKey(pathValues) === pathKey(search ? currentValue : currentValue.slice(0, path.length));
436
+ return /* @__PURE__ */ jsxs("div", {
437
+ id: `${id}-${level}-${itemIndex}`,
438
+ role: "option",
439
+ tabIndex: -1,
440
+ "aria-selected": selected,
441
+ "aria-describedby": !search && isBranch(item) ? `${id}-branch` : void 0,
442
+ "aria-disabled": item.disabled || void 0,
443
+ "data-selected": selected || void 0,
444
+ "data-disabled": item.disabled || void 0,
445
+ "data-highlighted": index === itemIndex || void 0,
446
+ className: cn(listItemClassName, "min-w-44 hover:bg-accent-hover"),
447
+ onMouseDown: (event) => {
448
+ if (searchable) event.preventDefault();
449
+ },
450
+ onClick: () => {
451
+ if (searchable) triggerRef.current?.focus();
452
+ else listRefs.current[level]?.focus();
453
+ setActive((prev) => ({
454
+ ...prev,
455
+ [level]: itemIndex
456
+ }));
457
+ choose(path, true);
458
+ },
459
+ onKeyDown: (event) => {
460
+ if (event.key === "Enter" || event.key === " ") {
461
+ event.preventDefault();
462
+ event.stopPropagation();
463
+ choose(path, true);
464
+ }
465
+ },
466
+ children: [
467
+ /* @__PURE__ */ jsx("span", {
468
+ className: "min-w-0 flex-1 break-words",
469
+ children: search ? path.map((part) => part.label).join(" / ") : item.label
470
+ }),
471
+ selected && /* @__PURE__ */ jsx(CheckIcon, {
472
+ "aria-hidden": true,
473
+ className: "size-4 shrink-0"
474
+ }),
475
+ !search && isBranch(item) && /* @__PURE__ */ jsx(ChevronRightIcon, {
476
+ "aria-hidden": true,
477
+ className: "size-4 shrink-0 rtl:rotate-180"
478
+ })
479
+ ]
480
+ }, pathKey(path.map((part) => part.value)));
481
+ })
482
+ }),
483
+ busy && /* @__PURE__ */ jsx("p", {
484
+ role: "status",
485
+ className: "px-4 py-3 text-sm text-muted-foreground",
486
+ children: messages.loading
487
+ }),
488
+ failed && /* @__PURE__ */ jsxs("div", {
489
+ className: "px-4 py-3 text-sm",
490
+ children: [/* @__PURE__ */ jsx("p", {
491
+ role: "status",
492
+ children: messages.error
493
+ }), /* @__PURE__ */ jsx("button", {
494
+ type: "button",
495
+ className: cn("mt-2 text-primary-text hover:underline", "focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40 aria-invalid:focus-visible:ring-destructive/40 data-invalid:focus-visible:ring-destructive/40"),
496
+ onClick: () => void data.load(column.parent),
497
+ children: messages.retry
498
+ })]
499
+ }),
500
+ !busy && !failed && column.items.length === 0 && /* @__PURE__ */ jsx("p", {
501
+ role: "status",
502
+ className: "px-4 py-3 text-sm text-muted-foreground",
503
+ children: messages.empty
504
+ })
505
+ ]
506
+ }, search ? "search" : pathKey(values));
507
+ })
508
+ })
509
+ ]
510
+ })]
511
+ });
512
+ }
513
+ export { Cascader };
@@ -0,0 +1,9 @@
1
+ import { ComponentProps } from "react";
2
+ export interface CodeProps extends Omit<ComponentProps<'code'>, 'children'> {
3
+ children: string;
4
+ /** Show a copy button beside the inline code. Defaults to false. */
5
+ copyable?: boolean;
6
+ /** Text to copy instead of the displayed code, including an empty string. */
7
+ copyValue?: string;
8
+ }
9
+ export declare function Code({ children, copyable, copyValue, className, ...props }: CodeProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { CopyButton } from "./copy-button.js";
3
+ import { cn } from "../../lib/cn.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ // src/components/code/code.tsx
6
+ function Code({ children, copyable = false, copyValue, className, ...props }) {
7
+ return /* @__PURE__ */ jsxs("code", {
8
+ className: cn("inline-flex max-w-full items-center gap-1 rounded-sm bg-muted px-1 py-0.5 align-baseline font-mono text-xs text-foreground", className),
9
+ ...props,
10
+ "data-slot": "code",
11
+ children: [/* @__PURE__ */ jsx("span", {
12
+ className: "min-w-0 whitespace-pre-wrap wrap-anywhere",
13
+ children
14
+ }), copyable && /* @__PURE__ */ jsx(CopyButton, {
15
+ value: copyValue ?? children,
16
+ className: "size-5"
17
+ })]
18
+ });
19
+ }
20
+ export { Code };
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ import { Button } from "../button/button.js";
3
+ import { useMessages } from "../../config/config-provider.js";
4
+ import { useClipboard } from "../../hooks/use-clipboard.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Check, Copy, TriangleAlert } from "lucide-react";
7
+ // src/components/code/copy-button.tsx
8
+ function CopyButtonContent({ value, className }) {
9
+ const { copy, status } = useClipboard();
10
+ const { code: messages } = useMessages();
11
+ const label = status === "copied" ? messages.copied : status === "error" ? messages.copyError : messages.copy;
12
+ return /* @__PURE__ */ jsxs("span", {
13
+ className: "inline-flex shrink-0 align-middle",
14
+ "data-slot": "code-copy",
15
+ children: [/* @__PURE__ */ jsx(Button, {
16
+ type: "button",
17
+ variant: "ghost",
18
+ size: "icon",
19
+ className,
20
+ "aria-label": label,
21
+ title: label,
22
+ "aria-busy": status === "copying" || void 0,
23
+ onClick: () => {
24
+ copy(value);
25
+ },
26
+ "data-status": status,
27
+ children: /* @__PURE__ */ jsx(status === "copied" ? Check : status === "error" ? TriangleAlert : Copy, {
28
+ "aria-hidden": "true",
29
+ className: status === "error" ? "text-destructive-text" : void 0
30
+ })
31
+ }), /* @__PURE__ */ jsx("span", {
32
+ className: "sr-only",
33
+ role: "status",
34
+ "aria-live": "polite",
35
+ children: status === "copied" || status === "error" ? label : ""
36
+ })]
37
+ });
38
+ }
39
+ function CopyButton(props) {
40
+ return /* @__PURE__ */ jsx(CopyButtonContent, { ...props }, props.value);
41
+ }
42
+ export { CopyButton };
@@ -0,0 +1,16 @@
1
+ import { ComponentProps } from "react";
2
+ export interface CodeBlockProps extends Omit<ComponentProps<'div'>, 'children'> {
3
+ code: string;
4
+ /** Syntax language or alias. Unknown languages render as plain text. */
5
+ language?: string;
6
+ /** Show a copy button. Defaults to true. */
7
+ copyable?: boolean;
8
+ /** Text to copy instead of the displayed code, including an empty string. */
9
+ copyValue?: string;
10
+ showLineNumbers?: boolean;
11
+ /** Wrap long lines instead of scrolling horizontally. */
12
+ wrapLines?: boolean;
13
+ /** Props for the scrollable pre element, including its accessible name and maximum height. */
14
+ preProps?: ComponentProps<'pre'>;
15
+ }
16
+ export declare function CodeBlock({ code, language, copyable, copyValue, showLineNumbers, wrapLines, preProps, className, ...props }: CodeBlockProps): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ "use client";
2
+ import { highlightLines, tokenClassName } from "./highlight.js";
3
+ import { CopyButton } from "../code/copy-button.js";
4
+ import { cn } from "../../lib/cn.js";
5
+ import { useMemo } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ // src/components/code-block/code-block.tsx
8
+ function CodeBlock({ code, language = "text", copyable = true, copyValue, showLineNumbers = false, wrapLines = false, preProps, className, ...props }) {
9
+ const lines = useMemo(() => highlightLines(code, language), [code, language]);
10
+ return /* @__PURE__ */ jsxs("div", {
11
+ className: cn("flex min-w-0 max-w-full items-start overflow-hidden rounded-lg bg-muted text-foreground", className),
12
+ ...props,
13
+ "data-slot": "code-block",
14
+ children: [/* @__PURE__ */ jsx("pre", {
15
+ tabIndex: 0,
16
+ ...preProps,
17
+ className: cn("m-0 min-w-0 flex-1 overflow-auto p-3 font-mono text-sm leading-6 outline-none focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:ring-inset", preProps?.className),
18
+ "data-slot": "code-block-pre",
19
+ children: /* @__PURE__ */ jsx("code", {
20
+ className: cn("block", wrapLines ? "whitespace-pre-wrap wrap-anywhere" : "w-max min-w-full whitespace-pre"),
21
+ children: lines.map((line, index) => /* @__PURE__ */ jsxs("span", {
22
+ className: "flex min-h-6",
23
+ "data-slot": "code-block-line",
24
+ children: [showLineNumbers && /* @__PURE__ */ jsx("span", {
25
+ "aria-hidden": "true",
26
+ className: "me-4 shrink-0 text-end text-muted-foreground select-none",
27
+ style: { width: `${String(lines.length).length}ch` },
28
+ "data-slot": "code-block-line-number",
29
+ children: index + 1
30
+ }), /* @__PURE__ */ jsx("span", {
31
+ className: "min-w-0 flex-1",
32
+ children: line.map((token, tokenIndex) => /* @__PURE__ */ jsx("span", {
33
+ className: tokenClassName(token.types),
34
+ children: token.content
35
+ }, tokenIndex))
36
+ })]
37
+ }, index))
38
+ })
39
+ }), copyable && /* @__PURE__ */ jsx(CopyButton, {
40
+ value: copyValue ?? code,
41
+ className: "m-1"
42
+ })]
43
+ });
44
+ }
45
+ export { CodeBlock };