@robert-stackflow/oneui 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.
Files changed (94) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +58 -2
  3. package/components/AppShell.d.ts +11 -4
  4. package/components/AppShell.js +79 -54
  5. package/components/AppShell.module.css +16 -4
  6. package/components/AppearanceSettings.js +1 -1
  7. package/components/Button.d.ts +9 -2
  8. package/components/Button.js +30 -7
  9. package/components/Button.module.css +16 -7
  10. package/components/Calendar.d.ts +2 -1
  11. package/components/Calendar.js +4 -3
  12. package/components/Card.d.ts +2 -1
  13. package/components/Card.js +13 -3
  14. package/components/Card.module.css +19 -0
  15. package/components/Checkbox.module.css +3 -3
  16. package/components/Combobox.d.ts +4 -2
  17. package/components/Combobox.js +162 -76
  18. package/components/Combobox.module.css +5 -7
  19. package/components/Control.module.css +18 -5
  20. package/components/DatePicker.d.ts +3 -0
  21. package/components/DatePicker.js +41 -21
  22. package/components/DatePicker.module.css +3 -0
  23. package/components/Dialog.d.ts +6 -1
  24. package/components/Dialog.js +19 -6
  25. package/components/Dialog.module.css +17 -0
  26. package/components/DialogVariants.d.ts +27 -0
  27. package/components/DialogVariants.js +161 -0
  28. package/components/DialogVariants.module.css +29 -0
  29. package/components/HierarchyPicker.module.css +35 -0
  30. package/components/IconButton.module.css +5 -2
  31. package/components/IconPicker.d.ts +14 -4
  32. package/components/IconPicker.js +67 -25
  33. package/components/Indicator.module.css +1 -1
  34. package/components/Input.d.ts +2 -1
  35. package/components/Input.js +12 -4
  36. package/components/Input.module.css +18 -1
  37. package/components/LoadingState.d.ts +3 -1
  38. package/components/LoadingState.js +26 -15
  39. package/components/LoadingState.module.css +3 -0
  40. package/components/MenuItem.d.ts +2 -1
  41. package/components/MenuItem.js +2 -0
  42. package/components/MenuItem.module.css +9 -0
  43. package/components/MultiSelect.d.ts +2 -1
  44. package/components/MultiSelect.js +66 -2
  45. package/components/MultiSelect.module.css +38 -0
  46. package/components/NumberInput.js +1 -0
  47. package/components/OptionList.module.css +12 -0
  48. package/components/PasswordField.js +1 -0
  49. package/components/PinInput.js +1 -0
  50. package/components/Popover.d.ts +4 -1
  51. package/components/Popover.js +11 -5
  52. package/components/Popover.module.css +3 -0
  53. package/components/RadioGroup.module.css +2 -2
  54. package/components/SearchField.js +1 -0
  55. package/components/Select.js +1 -0
  56. package/components/Select.module.css +3 -0
  57. package/components/SelectionTag.d.ts +8 -0
  58. package/components/SelectionTag.js +39 -0
  59. package/components/SettingsLayout.d.ts +3 -1
  60. package/components/SettingsLayout.js +19 -3
  61. package/components/SettingsLayout.module.css +20 -0
  62. package/components/Switch.module.css +8 -5
  63. package/components/Table.d.ts +6 -2
  64. package/components/Table.js +14 -4
  65. package/components/TagInput.js +11 -23
  66. package/components/TagInput.module.css +1 -1
  67. package/components/ThemeProvider.js +17 -3
  68. package/components/ToastProvider.d.ts +4 -4
  69. package/components/ToastProvider.js +19 -22
  70. package/components/TreeGuides.d.ts +9 -0
  71. package/components/TreeGuides.js +39 -0
  72. package/components/TreeGuides.module.css +45 -0
  73. package/components/TreeSelect.d.ts +3 -0
  74. package/components/TreeSelect.js +49 -21
  75. package/components/TreeTable.d.ts +15 -0
  76. package/components/TreeTable.js +151 -0
  77. package/components/TreeTable.module.css +53 -0
  78. package/components/VirtualPickerList.d.ts +2 -0
  79. package/components/VirtualPickerList.js +1 -1
  80. package/components/VirtualPickerList.module.css +4 -0
  81. package/components/appearance.d.ts +1 -0
  82. package/components/appearance.js +15 -6
  83. package/components/calendarNow.d.ts +5 -0
  84. package/components/calendarNow.js +27 -0
  85. package/components/createToaster.d.ts +12 -0
  86. package/components/createToaster.js +40 -0
  87. package/components/hierarchy.d.ts +1 -0
  88. package/components/index.d.ts +6 -3
  89. package/components/index.js +13 -1
  90. package/components/usePopover.d.ts +2 -2
  91. package/package.json +36 -4
  92. package/styles/base.css +20 -3
  93. package/styles/blue.css +49 -35
  94. package/styles/minimal.css +2 -1
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
+ import { type ToastController, type ToastNotify } from "./createToaster";
2
3
  export type ToastPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
3
4
  export type ToastTone = "neutral" | "success" | "info" | "warning" | "error";
4
5
  export type ToastAction = {
@@ -27,9 +28,8 @@ export type ToastMessage = {
27
28
  options?: ToastOptions;
28
29
  closing?: boolean;
29
30
  };
30
- type Notify = (message: string, options?: ToastOptions | ToastTone) => () => void;
31
- export declare function ToastProvider({ children }: {
31
+ export declare function ToastProvider({ children, controller, }: {
32
32
  children: ReactNode;
33
+ controller?: ToastController;
33
34
  }): import("react").JSX.Element;
34
- export declare const useToast: () => Notify;
35
- export {};
35
+ export declare const useToast: () => ToastNotify;
@@ -1,42 +1,39 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import {
3
3
  createContext,
4
- useCallback,
5
4
  useContext,
6
- useRef,
7
- useState
5
+ useState,
6
+ useSyncExternalStore
8
7
  } from "react";
9
8
  import { useUIConfig } from "./UIProvider.js";
10
9
  import { Toast } from "./Toast.js";
10
+ import {
11
+ createToaster
12
+ } from "./createToaster.js";
11
13
  const Context = createContext(() => () => {
12
14
  });
13
- function ToastProvider({ children }) {
15
+ const serverSnapshot = () => null;
16
+ function ToastProvider({
17
+ children,
18
+ controller
19
+ }) {
14
20
  const config = useUIConfig();
15
- const [toast, setToast] = useState(null), counter = useRef(0);
16
- const notify = useCallback((message, options) => {
17
- const id = ++counter.current;
18
- setToast({
19
- message,
20
- id,
21
- options: typeof options === "string" ? { tone: options } : options
22
- });
23
- return () => setToast(
24
- (current) => current?.id === id ? { ...current, closing: true } : current
25
- );
26
- }, []);
27
- const clear = useCallback(
28
- (id) => setToast((current) => current?.id === id ? null : current),
29
- []
21
+ const [local] = useState(createToaster), source = controller || local;
22
+ const toast = useSyncExternalStore(
23
+ source.subscribe,
24
+ source.getSnapshot,
25
+ serverSnapshot
30
26
  );
31
- return /* @__PURE__ */ jsxs(Context, { value: notify, children: [
27
+ return /* @__PURE__ */ jsxs(Context, { value: source.notify, children: [
32
28
  children,
33
29
  /* @__PURE__ */ jsx(
34
30
  Toast,
35
31
  {
36
32
  toast,
37
33
  position: toast?.options?.position || config.toastPosition,
38
- onDismiss: clear
39
- }
34
+ onDismiss: source.clear
35
+ },
36
+ source.id
40
37
  )
41
38
  ] });
42
39
  }
@@ -0,0 +1,9 @@
1
+ /** Shared connector geometry for hierarchy menus and tables. */
2
+ export declare function TreeGuides({ depth, continuations, last, expanded, indent, offset, }: {
3
+ depth: number;
4
+ continuations: boolean[];
5
+ last: boolean;
6
+ expanded?: boolean;
7
+ indent?: number;
8
+ offset?: number;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import s from "./TreeGuides.module.css";
3
+ function TreeGuides({
4
+ depth,
5
+ continuations,
6
+ last,
7
+ expanded,
8
+ indent = 24,
9
+ offset = 12
10
+ }) {
11
+ return /* @__PURE__ */ jsxs(
12
+ "span",
13
+ {
14
+ "aria-hidden": "true",
15
+ className: s.guides,
16
+ style: {
17
+ "--tree-indent": indent + "px",
18
+ "--tree-offset": offset + "px"
19
+ },
20
+ children: [
21
+ continuations.map(
22
+ (visible, i) => visible && /* @__PURE__ */ jsx("i", { className: s.line, style: { left: i * indent } }, i)
23
+ ),
24
+ depth > 0 && /* @__PURE__ */ jsx(
25
+ "i",
26
+ {
27
+ className: s.elbow,
28
+ "data-last": last,
29
+ style: { left: (depth - 1) * indent }
30
+ }
31
+ ),
32
+ expanded && /* @__PURE__ */ jsx("i", { className: s.stem, style: { left: depth * indent } })
33
+ ]
34
+ }
35
+ );
36
+ }
37
+ export {
38
+ TreeGuides
39
+ };
@@ -0,0 +1,45 @@
1
+ .guides {
2
+ position: absolute;
3
+ inset-block: 0;
4
+ left: var(--tree-offset);
5
+ width: 0;
6
+ pointer-events: none;
7
+ color: var(--line);
8
+ }
9
+ .line {
10
+ position: absolute;
11
+ top: 0;
12
+ bottom: 0;
13
+ border-left: 1px solid currentColor;
14
+ }
15
+ .elbow {
16
+ position: absolute;
17
+ top: 0;
18
+ bottom: 0;
19
+ width: var(--tree-indent);
20
+ }
21
+ .elbow::before {
22
+ content: "";
23
+ position: absolute;
24
+ top: 0;
25
+ height: 50%;
26
+ left: 0;
27
+ right: var(--tree-elbow-gap, 0px);
28
+ border-left: 1px solid currentColor;
29
+ border-bottom: 1px solid currentColor;
30
+ border-bottom-left-radius: 8px;
31
+ }
32
+ .elbow:not([data-last="true"])::after {
33
+ content: "";
34
+ position: absolute;
35
+ top: 50%;
36
+ bottom: 0;
37
+ left: 0;
38
+ border-left: 1px solid currentColor;
39
+ }
40
+ .stem {
41
+ position: absolute;
42
+ top: calc(50% + var(--tree-stem-inset, 0px));
43
+ bottom: 0;
44
+ border-left: 1px solid currentColor;
45
+ }
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type FieldOptions } from "./field";
2
3
  import { type HierarchyLoader, type HierarchyOption } from "./hierarchy";
3
4
  type Base = FieldOptions & {
@@ -8,6 +9,8 @@ type Base = FieldOptions & {
8
9
  placeholder?: string;
9
10
  loadChildren?: HierarchyLoader;
10
11
  defaultExpanded?: string[];
12
+ variant?: "tree" | "category";
13
+ icon?: ReactNode;
11
14
  };
12
15
  export type TreeSelectProps = Base & ({
13
16
  multiple?: false;
@@ -1,5 +1,10 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useId, useMemo, useRef, useState } from "react";
2
+ import {
3
+ useId,
4
+ useMemo,
5
+ useRef,
6
+ useState
7
+ } from "react";
3
8
  import { ChevronDown, Minus } from "lucide-react";
4
9
  import { SelectTrigger } from "./SelectTrigger.js";
5
10
  import { Popover } from "./Popover.js";
@@ -14,6 +19,8 @@ import {
14
19
  hierarchyMatches
15
20
  } from "./hierarchy.js";
16
21
  import s from "./HierarchyPicker.module.css";
22
+ import { Badge } from "./Badge.js";
23
+ import { TreeGuides } from "./TreeGuides.js";
17
24
  function TreeSelect(props) {
18
25
  const {
19
26
  options,
@@ -22,7 +29,8 @@ function TreeSelect(props) {
22
29
  clearable = true,
23
30
  placeholder = "\u8BF7\u9009\u62E9",
24
31
  loadChildren,
25
- defaultExpanded = []
32
+ defaultExpanded = [],
33
+ variant = "tree"
26
34
  } = props;
27
35
  const field = useFieldControl(props), pop = usePopover(field.disabled || field.readOnly), id = useId(), list = useRef(null), [query, setQuery] = useState(""), [expanded, setExpanded] = useState(new Set(defaultExpanded)), [focused, setFocused] = useState("");
28
36
  const data = useHierarchy(options, loadChildren), selected = props.multiple ? props.value : props.value ? [props.value] : [];
@@ -34,6 +42,8 @@ function TreeSelect(props) {
34
42
  (entry) => (!matches || matches.has(entry.node.value)) && (query.trim() || entry.path.slice(0, -1).every((node) => expanded.has(node.value)))
35
43
  );
36
44
  }, [data.entries, query, expanded]);
45
+ const siblings = (entry) => entry.parent ? data.children(data.entries.get(entry.parent).node) || [] : options;
46
+ const last = (entry) => siblings(entry).at(-1)?.value === entry.node.value;
37
47
  const close = pop.closeAndFocus;
38
48
  const show = () => {
39
49
  if (field.disabled || field.readOnly) return;
@@ -115,6 +125,7 @@ function TreeSelect(props) {
115
125
  ...fieldTriggerProps(field),
116
126
  ref: pop.anchor,
117
127
  expanded: pop.open,
128
+ icon: props.icon,
118
129
  className: s.trigger,
119
130
  "aria-haspopup": "dialog",
120
131
  "aria-controls": id,
@@ -229,11 +240,13 @@ function TreeSelect(props) {
229
240
  "aria-disabled": entry.disabled || void 0,
230
241
  tabIndex: entry.node.value === active ? 0 : -1,
231
242
  "data-value": entry.node.value,
232
- className: `${s.row} ${s.treeRow}`,
243
+ className: `${s.row} ${s.treeRow} ${variant === "category" ? s.categoryRow : ""}`,
244
+ "data-branch": entry.branch,
233
245
  style: { "--level": entry.path.length - 1 },
234
246
  label: entry.node.label,
235
247
  description: query.trim() ? entry.path.slice(0, -1).map((n) => n.label).join(" / ") : void 0,
236
248
  icon: entry.node.icon,
249
+ meta: variant === "category" && entry.branch ? /* @__PURE__ */ jsx(Badge, { children: entry.node.count ?? data.children(entry.node)?.length ?? 0 }) : void 0,
237
250
  selected: state === true,
238
251
  selection: props.multiple ? "checkbox" : "check",
239
252
  disabled: entry.disabled,
@@ -244,23 +257,38 @@ function TreeSelect(props) {
244
257
  focus(entry.node.value);
245
258
  choose(entry);
246
259
  },
247
- prefix: entry.branch ? /* @__PURE__ */ jsx(
248
- "button",
249
- {
250
- type: "button",
251
- tabIndex: -1,
252
- className: s.expander,
253
- "data-expanded": isExpanded,
254
- "aria-label": `${isExpanded ? "\u6298\u53E0" : "\u5C55\u5F00"} ${entry.node.label}`,
255
- disabled: entry.disabled || data.loading.has(entry.node.value),
256
- onClick: (event) => {
257
- event.stopPropagation();
258
- focus(entry.node.value);
259
- toggleExpand(entry);
260
- },
261
- children: data.loading.has(entry.node.value) ? /* @__PURE__ */ jsx(Spinner, { size: 12, decorative: true }) : /* @__PURE__ */ jsx(ChevronDown, {})
262
- }
263
- ) : /* @__PURE__ */ jsx("span", { className: s.spacer })
260
+ prefix: /* @__PURE__ */ jsxs(Fragment, { children: [
261
+ variant === "category" && /* @__PURE__ */ jsx(
262
+ TreeGuides,
263
+ {
264
+ depth: entry.path.length - 1,
265
+ indent: 18,
266
+ offset: 20,
267
+ last: last(entry),
268
+ expanded: entry.branch && isExpanded,
269
+ continuations: entry.path.slice(1, -1).map(
270
+ (node) => !last(data.entries.get(node.value))
271
+ )
272
+ }
273
+ ),
274
+ entry.branch ? /* @__PURE__ */ jsx(
275
+ "button",
276
+ {
277
+ type: "button",
278
+ tabIndex: -1,
279
+ className: s.expander,
280
+ "data-expanded": isExpanded,
281
+ "aria-label": `${isExpanded ? "\u6298\u53E0" : "\u5C55\u5F00"} ${entry.node.label}`,
282
+ disabled: entry.disabled || data.loading.has(entry.node.value),
283
+ onClick: (event) => {
284
+ event.stopPropagation();
285
+ focus(entry.node.value);
286
+ toggleExpand(entry);
287
+ },
288
+ children: data.loading.has(entry.node.value) ? /* @__PURE__ */ jsx(Spinner, { size: 12, decorative: true }) : /* @__PURE__ */ jsx(ChevronDown, {})
289
+ }
290
+ ) : /* @__PURE__ */ jsx("span", { className: s.spacer })
291
+ ] })
264
292
  }
265
293
  ),
266
294
  isExpanded && error && /* @__PURE__ */ jsxs("div", { role: "alert", className: s.error, children: [
@@ -282,7 +310,7 @@ function TreeSelect(props) {
282
310
  ),
283
311
  /* @__PURE__ */ jsxs("div", { className: s.footer, children: [
284
312
  /* @__PURE__ */ jsx("span", { children: props.multiple ? `${selected.length} \u9879\u5DF2\u9009` : "" }),
285
- /* @__PURE__ */ jsxs("div", { children: [
313
+ /* @__PURE__ */ jsxs("div", { className: s.footerActions, children: [
286
314
  clearable && /* @__PURE__ */ jsx(
287
315
  Button,
288
316
  {
@@ -0,0 +1,15 @@
1
+ import { type Column } from "./Table";
2
+ export type TreeTableProps<T, K extends string | number> = {
3
+ rows: T[];
4
+ columns: Column<T>[];
5
+ rowKey(row: T): K;
6
+ getChildren(row: T): T[] | undefined;
7
+ rowLabel(row: T): string;
8
+ label?: string;
9
+ treeColumn?: string;
10
+ expanded?: K[];
11
+ defaultExpanded?: K[];
12
+ defaultExpandAll?: boolean;
13
+ onExpandedChange?(keys: K[]): void;
14
+ };
15
+ export declare function TreeTable<T, K extends string | number>({ rows, columns, rowKey, getChildren, rowLabel, label, treeColumn, expanded, defaultExpanded, defaultExpandAll, onExpandedChange, }: TreeTableProps<T, K>): import("react").JSX.Element;
@@ -0,0 +1,151 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { Table } from "./Table.js";
4
+ import { IconButton } from "./IconButton.js";
5
+ import { DisclosureChevron } from "./DisclosureChevron.js";
6
+ import { TreeGuides } from "./TreeGuides.js";
7
+ import s from "./TreeTable.module.css";
8
+ function TreeTable({
9
+ rows,
10
+ columns,
11
+ rowKey,
12
+ getChildren,
13
+ rowLabel,
14
+ label = "\u5C42\u7EA7\u8868\u683C",
15
+ treeColumn = columns[0]?.key,
16
+ expanded,
17
+ defaultExpanded = [],
18
+ defaultExpandAll = false,
19
+ onExpandedChange
20
+ }) {
21
+ const [local, setLocal] = useState(() => {
22
+ if (!defaultExpandAll) return defaultExpanded;
23
+ const keys2 = [], seen = /* @__PURE__ */ new Set();
24
+ const walk = (nodes) => nodes.forEach((row) => {
25
+ const key = rowKey(row);
26
+ if (seen.has(key)) return;
27
+ seen.add(key);
28
+ const children = getChildren(row);
29
+ if (children?.length) {
30
+ keys2.push(key);
31
+ walk(children);
32
+ }
33
+ });
34
+ walk(rows);
35
+ return keys2;
36
+ });
37
+ const keys = expanded ?? local, open = new Set(keys);
38
+ const root = useRef(null), [focused, setFocused] = useState();
39
+ const visible = useMemo(() => {
40
+ const entries = [], seen = /* @__PURE__ */ new Set();
41
+ const walk = (nodes, depth, continuations) => nodes.forEach((row, i) => {
42
+ const key = rowKey(row);
43
+ if (seen.has(key)) return;
44
+ seen.add(key);
45
+ const children = getChildren(row) || [], last = i === nodes.length - 1;
46
+ entries.push({
47
+ row,
48
+ key,
49
+ depth,
50
+ last,
51
+ continuations,
52
+ children,
53
+ position: i + 1,
54
+ size: nodes.length
55
+ });
56
+ if (children.length && open.has(key))
57
+ walk(
58
+ children,
59
+ depth + 1,
60
+ depth === 0 ? [] : [...continuations, !last]
61
+ );
62
+ });
63
+ walk(rows, 0, []);
64
+ return entries;
65
+ }, [rows, getChildren, rowKey, keys]);
66
+ const toggle = (key) => {
67
+ const next = open.has(key) ? keys.filter((k) => k !== key) : [...keys, key];
68
+ if (expanded === void 0) setLocal(next);
69
+ onExpandedChange?.(next);
70
+ };
71
+ const focus = (key) => {
72
+ setFocused(key);
73
+ const index = visible.findIndex((entry) => entry.key === key);
74
+ root.current?.querySelectorAll("tbody tr")[index]?.focus();
75
+ };
76
+ const active = visible.some((entry) => entry.key === focused) ? focused : visible[0]?.key;
77
+ return /* @__PURE__ */ jsx("div", { className: s.root, ref: root, children: /* @__PURE__ */ jsx(
78
+ Table,
79
+ {
80
+ label,
81
+ role: "treegrid",
82
+ rows: visible,
83
+ rowKey: (entry) => typeof entry.key + ":" + String(entry.key),
84
+ rowAttributes: (entry) => ({
85
+ "aria-level": entry.depth + 1,
86
+ "aria-posinset": entry.position,
87
+ "aria-setsize": entry.size,
88
+ "aria-expanded": entry.children.length ? open.has(entry.key) : void 0,
89
+ "data-tree-root": entry.depth === 0,
90
+ tabIndex: entry.key === active ? 0 : -1,
91
+ onFocus: (event) => {
92
+ if (event.target === event.currentTarget) setFocused(entry.key);
93
+ },
94
+ onKeyDown: (event) => {
95
+ if (event.target !== event.currentTarget) return;
96
+ const index = visible.indexOf(entry);
97
+ let next;
98
+ if (event.key === "ArrowDown")
99
+ next = visible[Math.min(visible.length - 1, index + 1)];
100
+ else if (event.key === "ArrowUp")
101
+ next = visible[Math.max(0, index - 1)];
102
+ else if (event.key === "Home") next = visible[0];
103
+ else if (event.key === "End") next = visible.at(-1);
104
+ else if (event.key === "ArrowRight") {
105
+ if (entry.children.length && !open.has(entry.key))
106
+ toggle(entry.key);
107
+ else if (visible[index + 1]?.depth > entry.depth)
108
+ next = visible[index + 1];
109
+ } else if (event.key === "ArrowLeft") {
110
+ if (entry.children.length && open.has(entry.key))
111
+ toggle(entry.key);
112
+ else
113
+ next = visible.slice(0, index).reverse().find((parent) => parent.depth < entry.depth);
114
+ } else return;
115
+ event.preventDefault();
116
+ if (next) focus(next.key);
117
+ }
118
+ }),
119
+ columns: columns.map((column) => ({
120
+ key: column.key,
121
+ title: column.title,
122
+ align: column.align,
123
+ width: column.width,
124
+ render: (entry) => column.key !== treeColumn ? column.render(entry.row) : /* @__PURE__ */ jsxs("div", { className: s.cell, style: { paddingLeft: entry.depth * 24 }, children: [
125
+ /* @__PURE__ */ jsx(
126
+ TreeGuides,
127
+ {
128
+ depth: entry.depth,
129
+ continuations: entry.continuations,
130
+ last: entry.last
131
+ }
132
+ ),
133
+ entry.children.length ? /* @__PURE__ */ jsx(
134
+ IconButton,
135
+ {
136
+ label: `${open.has(entry.key) ? "\u6298\u53E0" : "\u5C55\u5F00"} ${rowLabel(entry.row)}`,
137
+ className: s.expander,
138
+ onClick: () => toggle(entry.key),
139
+ "aria-expanded": open.has(entry.key),
140
+ children: /* @__PURE__ */ jsx(DisclosureChevron, { expanded: open.has(entry.key) })
141
+ }
142
+ ) : /* @__PURE__ */ jsx("span", { className: s.spacer }),
143
+ /* @__PURE__ */ jsx("div", { className: s.content, children: column.render(entry.row) })
144
+ ] })
145
+ }))
146
+ }
147
+ ) });
148
+ }
149
+ export {
150
+ TreeTable
151
+ };
@@ -0,0 +1,53 @@
1
+ .root {
2
+ border: 1px solid var(--line);
3
+ border-radius: var(--card-radius);
4
+ background: var(--surface);
5
+ overflow: hidden;
6
+ box-shadow: var(--card-shadow, none);
7
+ }
8
+ .root table td {
9
+ padding: 0 18px;
10
+ border-bottom: 0;
11
+ }
12
+ .root table th {
13
+ padding: 14px 18px;
14
+ }
15
+ .root tr:focus-visible {
16
+ outline: 1px solid var(--accent);
17
+ outline-offset: -1px;
18
+ }
19
+ .root table tr[data-tree-root="true"]:not(:first-child) td {
20
+ border-top: 1px solid var(--line);
21
+ }
22
+ .cell {
23
+ --tree-elbow-gap: 8px;
24
+ position: relative;
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 8px;
28
+ min-height: 72px;
29
+ padding-block: 14px;
30
+ min-width: 260px;
31
+ white-space: normal;
32
+ }
33
+ .expander,
34
+ .spacer {
35
+ flex: 0 0 24px;
36
+ width: 24px;
37
+ height: 26px;
38
+ }
39
+ .content {
40
+ position: relative;
41
+ flex: 1;
42
+ min-width: 0;
43
+ }
44
+ .expander svg {
45
+ width: 14px;
46
+ height: 14px;
47
+ }
48
+ .expander[aria-expanded="false"] svg {
49
+ rotate: -90deg;
50
+ }
51
+ .expander[aria-expanded="true"] svg {
52
+ rotate: 0deg;
53
+ }
@@ -1,3 +1,4 @@
1
+ import { type ReactNode } from "react";
1
2
  import { type LucideIconData } from "lucide-react";
2
3
  export type PickerCatalogItem = {
3
4
  id: string;
@@ -5,6 +6,7 @@ export type PickerCatalogItem = {
5
6
  search: string;
6
7
  glyph?: string;
7
8
  icon?: LucideIconData;
9
+ content?: ReactNode;
8
10
  variants?: {
9
11
  glyph: string;
10
12
  label: string;
@@ -44,7 +44,7 @@ const Cell = memo(function Cell2({
44
44
  }
45
45
  },
46
46
  children: [
47
- item.icon ? /* @__PURE__ */ jsx(Icon, { icon: item.icon }) : item.glyph,
47
+ item.content !== void 0 ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: item.content }) : item.icon ? /* @__PURE__ */ jsx(Icon, { icon: item.icon }) : item.glyph,
48
48
  item.variants && item.variants.length > 1 && /* @__PURE__ */ jsx("span", { className: s.variantMark, "aria-hidden": "true" })
49
49
  ]
50
50
  }
@@ -1,4 +1,8 @@
1
1
  .body {
2
+ --blocked-background: transparent;
3
+ --blocked-border: 0;
4
+ --blocked-selected: var(--selected);
5
+ --blocked-shadow: none;
2
6
  display: flex;
3
7
  gap: 12px;
4
8
  min-height: 0;
@@ -1,6 +1,7 @@
1
1
  import type { UITheme, ColorMode } from "./ThemeProvider";
2
2
  import type { UIConfig } from "./UIProvider";
3
3
  export interface AppearancePreferences {
4
+ paletteVersion?: 2;
4
5
  designTheme: UITheme;
5
6
  mode: ColorMode;
6
7
  accent: string;
@@ -1,13 +1,14 @@
1
1
  function appearanceDefaults(designTheme = "minimal") {
2
2
  const blue = designTheme === "blue";
3
3
  return {
4
+ paletteVersion: 2,
4
5
  designTheme,
5
6
  mode: "system",
6
- accent: blue ? "#5269b6" : "#303030",
7
+ accent: blue ? "#5067b8" : "#303030",
7
8
  lightBackground: "#ffffff",
8
- darkBackground: blue ? "#19212e" : "#1e1e1f",
9
- lightForeground: blue ? "#263149" : "#202020",
10
- darkForeground: blue ? "#e0e7f2" : "#f4f4f5",
9
+ darkBackground: blue ? "#19212f" : "#1e1e1f",
10
+ lightForeground: blue ? "#25324a" : "#202020",
11
+ darkForeground: blue ? "#e1e8f4" : "#f4f4f5",
11
12
  font: "system",
12
13
  density: "comfortable",
13
14
  radius: blue ? 9 : 10,
@@ -21,6 +22,12 @@ function normalizeAppearance(input, fallback = "minimal") {
21
22
  const d = appearanceDefaults(
22
23
  x.designTheme === "minimal" || x.designTheme === "blue" ? x.designTheme : fallback
23
24
  );
25
+ const legacyBlue = d.designTheme === "blue" && x.paletteVersion !== 2 ? {
26
+ accent: "#5269b6",
27
+ darkBackground: "#19212e",
28
+ lightForeground: "#263149",
29
+ darkForeground: "#e0e7f2"
30
+ } : {};
24
31
  if (["light", "dark", "system"].includes(String(x.mode)))
25
32
  d.mode = x.mode;
26
33
  for (const key of [
@@ -30,8 +37,10 @@ function normalizeAppearance(input, fallback = "minimal") {
30
37
  "lightForeground",
31
38
  "darkForeground"
32
39
  ])
33
- if (typeof x[key] === "string" && /^#[\da-f]{6}$/i.test(x[key]))
34
- d[key] = x[key];
40
+ if (typeof x[key] === "string" && /^#[\da-f]{6}$/i.test(x[key])) {
41
+ if (x[key].toLowerCase() !== legacyBlue[key])
42
+ d[key] = x[key];
43
+ }
35
44
  if (["system", "Segoe UI", "Microsoft YaHei UI", "sans-serif"].includes(
36
45
  String(x.font)
37
46
  ))
@@ -0,0 +1,5 @@
1
+ /** Calendar fields, not an instant conversion; keep entered wall-clock values intact. */
2
+ export declare function calendarNow(timeZone?: string, instant?: Date): {
3
+ date: string;
4
+ time: string;
5
+ };
@@ -0,0 +1,27 @@
1
+ import { dateKey } from "./dateConstraints.js";
2
+ function calendarNow(timeZone, instant = /* @__PURE__ */ new Date()) {
3
+ if (!timeZone)
4
+ return {
5
+ date: dateKey(instant),
6
+ time: [instant.getHours(), instant.getMinutes(), instant.getSeconds()].map((part2) => String(part2).padStart(2, "0")).join(":")
7
+ };
8
+ const parts = new Intl.DateTimeFormat("en-CA", {
9
+ timeZone,
10
+ numberingSystem: "latn",
11
+ year: "numeric",
12
+ month: "2-digit",
13
+ day: "2-digit",
14
+ hour: "2-digit",
15
+ minute: "2-digit",
16
+ second: "2-digit",
17
+ hourCycle: "h23"
18
+ }).formatToParts(instant);
19
+ const part = (type) => parts.find((item) => item.type === type).value;
20
+ return {
21
+ date: part("year").padStart(4, "0") + "-" + part("month") + "-" + part("day"),
22
+ time: part("hour") + ":" + part("minute") + ":" + part("second")
23
+ };
24
+ }
25
+ export {
26
+ calendarNow
27
+ };