@robert-stackflow/oneui 0.1.0 → 0.2.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 (77) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +28 -1
  3. package/components/AppShell.module.css +3 -3
  4. package/components/AppearanceSettings.js +1 -1
  5. package/components/Button.d.ts +9 -2
  6. package/components/Button.js +30 -7
  7. package/components/Button.module.css +16 -7
  8. package/components/Card.d.ts +2 -1
  9. package/components/Card.js +13 -3
  10. package/components/Card.module.css +19 -0
  11. package/components/Checkbox.module.css +3 -3
  12. package/components/Combobox.d.ts +4 -2
  13. package/components/Combobox.js +162 -76
  14. package/components/Combobox.module.css +5 -7
  15. package/components/Control.module.css +18 -5
  16. package/components/Dialog.d.ts +6 -1
  17. package/components/Dialog.js +19 -6
  18. package/components/Dialog.module.css +17 -0
  19. package/components/DialogVariants.d.ts +27 -0
  20. package/components/DialogVariants.js +161 -0
  21. package/components/DialogVariants.module.css +29 -0
  22. package/components/HierarchyPicker.module.css +35 -0
  23. package/components/IconButton.module.css +5 -2
  24. package/components/Indicator.module.css +1 -1
  25. package/components/Input.d.ts +2 -1
  26. package/components/Input.js +12 -4
  27. package/components/Input.module.css +18 -1
  28. package/components/LoadingState.d.ts +3 -1
  29. package/components/LoadingState.js +26 -15
  30. package/components/LoadingState.module.css +3 -0
  31. package/components/MenuItem.d.ts +2 -1
  32. package/components/MenuItem.js +2 -0
  33. package/components/MenuItem.module.css +9 -0
  34. package/components/MultiSelect.d.ts +2 -1
  35. package/components/MultiSelect.js +66 -2
  36. package/components/MultiSelect.module.css +38 -0
  37. package/components/NumberInput.js +1 -0
  38. package/components/OptionList.module.css +12 -0
  39. package/components/PasswordField.js +1 -0
  40. package/components/PinInput.js +1 -0
  41. package/components/Popover.d.ts +4 -1
  42. package/components/Popover.js +11 -5
  43. package/components/Popover.module.css +3 -0
  44. package/components/RadioGroup.module.css +2 -2
  45. package/components/SearchField.js +1 -0
  46. package/components/Select.js +1 -0
  47. package/components/Select.module.css +3 -0
  48. package/components/SelectionTag.d.ts +8 -0
  49. package/components/SelectionTag.js +39 -0
  50. package/components/SettingsLayout.d.ts +3 -1
  51. package/components/SettingsLayout.js +19 -3
  52. package/components/SettingsLayout.module.css +20 -0
  53. package/components/Switch.module.css +8 -5
  54. package/components/Table.d.ts +6 -2
  55. package/components/Table.js +14 -4
  56. package/components/TagInput.js +11 -23
  57. package/components/TagInput.module.css +1 -1
  58. package/components/ThemeProvider.js +17 -3
  59. package/components/TreeGuides.d.ts +9 -0
  60. package/components/TreeGuides.js +39 -0
  61. package/components/TreeGuides.module.css +45 -0
  62. package/components/TreeSelect.d.ts +3 -0
  63. package/components/TreeSelect.js +49 -21
  64. package/components/TreeTable.d.ts +15 -0
  65. package/components/TreeTable.js +151 -0
  66. package/components/TreeTable.module.css +53 -0
  67. package/components/VirtualPickerList.module.css +4 -0
  68. package/components/appearance.d.ts +1 -0
  69. package/components/appearance.js +15 -6
  70. package/components/hierarchy.d.ts +1 -0
  71. package/components/index.d.ts +3 -1
  72. package/components/index.js +6 -0
  73. package/components/usePopover.d.ts +2 -2
  74. package/package.json +30 -3
  75. package/styles/base.css +20 -3
  76. package/styles/blue.css +49 -35
  77. package/styles/minimal.css +1 -1
@@ -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,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
  ))
@@ -3,6 +3,7 @@ export type HierarchyOption = {
3
3
  value: string;
4
4
  label: string;
5
5
  icon?: ReactNode;
6
+ count?: number;
6
7
  keywords?: string[];
7
8
  children?: HierarchyOption[];
8
9
  disabled?: boolean;
@@ -84,7 +84,7 @@ export type { FormController, FormRules, FieldRule } from "./useFormState";
84
84
  export { PinInput } from "./PinInput";
85
85
  export { ContributionCalendar } from "./ContributionCalendar";
86
86
  export type { ContributionDay } from "./contribution";
87
- export { Combobox } from "./Combobox";
87
+ export { Combobox, type ComboboxProps } from "./Combobox";
88
88
  export { TagInput } from "./TagInput";
89
89
  export { PasswordField } from "./PasswordField";
90
90
  export { Alert } from "./Alert";
@@ -148,3 +148,5 @@ export { SettingsLayout, SettingsCard } from "./SettingsLayout";
148
148
  export { AppearanceSettings } from "./AppearanceSettings";
149
149
  export { appearanceDefaults, normalizeAppearance } from "./appearance";
150
150
  export type { AppearancePreferences } from "./appearance";
151
+ export { MessageDialog, ConfirmDialog, FormDialog } from "./DialogVariants";
152
+ export { TreeTable, type TreeTableProps } from "./TreeTable";
@@ -104,6 +104,8 @@ import { AppShell, ShellHeader } from "./AppShell.js";
104
104
  import { SettingsLayout, SettingsCard } from "./SettingsLayout.js";
105
105
  import { AppearanceSettings } from "./AppearanceSettings.js";
106
106
  import { appearanceDefaults, normalizeAppearance } from "./appearance.js";
107
+ import { MessageDialog, ConfirmDialog, FormDialog } from "./DialogVariants.js";
108
+ import { TreeTable } from "./TreeTable.js";
107
109
  export {
108
110
  Accordion,
109
111
  Alert,
@@ -125,6 +127,7 @@ export {
125
127
  ColorPicker,
126
128
  Combobox,
127
129
  CommandPalette,
130
+ ConfirmDialog,
128
131
  ContextMenu,
129
132
  ContextMenuPanel,
130
133
  ContributionCalendar,
@@ -144,6 +147,7 @@ export {
144
147
  FilePicker,
145
148
  FileTree,
146
149
  Form,
150
+ FormDialog,
147
151
  FormErrorSummary,
148
152
  FormField,
149
153
  FormGroup,
@@ -155,6 +159,7 @@ export {
155
159
  LoadingState,
156
160
  Menu,
157
161
  MenuItem,
162
+ MessageDialog,
158
163
  MultiSelect,
159
164
  NumberInput,
160
165
  Page,
@@ -204,6 +209,7 @@ export {
204
209
  ToastProvider,
205
210
  Tooltip,
206
211
  TreeSelect,
212
+ TreeTable,
207
213
  UIProvider,
208
214
  UnderlinedTab,
209
215
  Upload,
@@ -1,5 +1,5 @@
1
- export declare function usePopover(blocked?: boolean): {
2
- anchor: import("react").RefObject<HTMLButtonElement | null>;
1
+ export declare function usePopover<T extends HTMLElement = HTMLButtonElement>(blocked?: boolean): {
2
+ anchor: import("react").RefObject<T | null>;
3
3
  open: boolean;
4
4
  close: () => void;
5
5
  closeAndFocus: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robert-stackflow/oneui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "OneUI React components, application shell and portable visual themes",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -763,13 +763,40 @@
763
763
  "types": "./components/appearance.d.ts",
764
764
  "import": "./components/appearance.js",
765
765
  "default": "./components/appearance.js"
766
- }
766
+ },
767
+ "./DialogVariants": {
768
+ "types": "./components/DialogVariants.d.ts",
769
+ "import": "./components/DialogVariants.js",
770
+ "default": "./components/DialogVariants.js"
771
+ },
772
+ "./MessageDialog": {
773
+ "types": "./components/DialogVariants.d.ts",
774
+ "import": "./components/DialogVariants.js",
775
+ "default": "./components/DialogVariants.js"
776
+ },
777
+ "./ConfirmDialog": {
778
+ "types": "./components/DialogVariants.d.ts",
779
+ "import": "./components/DialogVariants.js",
780
+ "default": "./components/DialogVariants.js"
781
+ },
782
+ "./FormDialog": {
783
+ "types": "./components/DialogVariants.d.ts",
784
+ "import": "./components/DialogVariants.js",
785
+ "default": "./components/DialogVariants.js"
786
+ },
787
+ "./TreeTable": {
788
+ "types": "./components/TreeTable.d.ts",
789
+ "import": "./components/TreeTable.js",
790
+ "default": "./components/TreeTable.js"
791
+ },
792
+ "./TreeTable.module.css": "./components/TreeTable.module.css"
767
793
  },
768
794
  "files": [
769
795
  "components",
770
796
  "styles",
771
797
  "assets",
772
- "README.md"
798
+ "README.md",
799
+ "CHANGELOG.md"
773
800
  ],
774
801
  "peerDependencies": {
775
802
  "react": "19.3.1",