@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,6 +1,6 @@
1
1
  import { type FieldOptions } from "./field";
2
2
  import type { SelectOption } from "./types";
3
- export declare function MultiSelect({ value, onChange, options, label, disabled, readOnly, required, name, id: controlId, error, hint, maxSelected, clearable, placeholder, ...aria }: FieldOptions & {
3
+ export declare function MultiSelect({ value, onChange, options, label, disabled, readOnly, required, name, id: controlId, error, hint, maxSelected, clearable, placeholder, display, ...aria }: FieldOptions & {
4
4
  value: string[];
5
5
  onChange(value: string[]): void;
6
6
  options: SelectOption[];
@@ -9,4 +9,5 @@ export declare function MultiSelect({ value, onChange, options, label, disabled,
9
9
  maxSelected?: number;
10
10
  clearable?: boolean;
11
11
  placeholder?: string;
12
+ display?: "summary" | "tags";
12
13
  }): import("react").JSX.Element;
@@ -9,6 +9,9 @@ import { Popover } from "./Popover.js";
9
9
  import { usePopover } from "./usePopover.js";
10
10
  import { Button } from "./Button.js";
11
11
  import s from "./MultiSelect.module.css";
12
+ import { SelectionTag } from "./SelectionTag.js";
13
+ import { DisclosureChevron } from "./DisclosureChevron.js";
14
+ import { joinIds } from "./field.js";
12
15
  function MultiSelect({
13
16
  value,
14
17
  onChange,
@@ -24,6 +27,7 @@ function MultiSelect({
24
27
  maxSelected = Infinity,
25
28
  clearable = true,
26
29
  placeholder = "\u8BF7\u9009\u62E9",
30
+ display = "summary",
27
31
  ...aria
28
32
  }) {
29
33
  const field = useFieldControl({
@@ -37,7 +41,10 @@ function MultiSelect({
37
41
  hint,
38
42
  ...aria
39
43
  });
40
- const pop = usePopover(field.disabled || field.readOnly), [query, setQuery] = useState(""), list = useRef(null), search = useRef(null), id = useId();
44
+ const pop = usePopover(
45
+ field.disabled || field.readOnly
46
+ ), [query, setQuery] = useState(""), list = useRef(null), search = useRef(null), id = useId();
47
+ const { disabled: triggerDisabled, ...tagField } = fieldTriggerProps(field);
41
48
  const reserveIcon = options.some((option) => !!option.icon);
42
49
  const filtered = options.filter(
43
50
  (item) => item.label.toLowerCase().includes(query.toLowerCase())
@@ -54,9 +61,65 @@ function MultiSelect({
54
61
  search.current?.focus();
55
62
  }
56
63
  }, [pop.open]);
64
+ const remove = (id2) => {
65
+ if (field.disabled || field.readOnly || options.find((option) => option.value === id2)?.disabled)
66
+ return;
67
+ onChange(value.filter((v) => v !== id2));
68
+ pop.anchor.current?.focus();
69
+ };
57
70
  const summary = value.length ? options.filter((item) => value.includes(item.value)).map((item) => item.label).slice(0, 2).join("\u3001") + (value.length > 2 ? ` +${value.length - 2}` : "") : placeholder;
58
71
  return /* @__PURE__ */ jsxs(Fragment, { children: [
59
- /* @__PURE__ */ jsx(
72
+ display === "tags" ? /* @__PURE__ */ jsxs(Fragment, { children: [
73
+ /* @__PURE__ */ jsxs(
74
+ "div",
75
+ {
76
+ ...tagField,
77
+ ref: pop.anchor,
78
+ role: "combobox",
79
+ "aria-expanded": pop.open,
80
+ "aria-controls": id,
81
+ "aria-haspopup": "dialog",
82
+ "aria-disabled": field.disabled || void 0,
83
+ "aria-describedby": joinIds(field.descriptionId, id + "-summary"),
84
+ "data-disabled": triggerDisabled,
85
+ "data-readonly": field.readOnly || void 0,
86
+ className: s.tagTrigger,
87
+ tabIndex: field.disabled ? -1 : 0,
88
+ onClick: pop.toggle,
89
+ onKeyDown: (event) => {
90
+ if (event.target !== event.currentTarget || field.disabled || field.readOnly)
91
+ return;
92
+ if (["ArrowDown", "Enter", " "].includes(event.key)) {
93
+ event.preventDefault();
94
+ pop.show();
95
+ }
96
+ if (event.key === "Backspace" && value.length) {
97
+ event.preventDefault();
98
+ remove(value[value.length - 1]);
99
+ }
100
+ },
101
+ children: [
102
+ value.map((id2) => {
103
+ const option = options.find((option2) => option2.value === id2);
104
+ return /* @__PURE__ */ jsx(
105
+ SelectionTag,
106
+ {
107
+ label: option?.label || id2,
108
+ icon: option?.icon,
109
+ disabled: field.disabled || option?.disabled,
110
+ onRemove: field.readOnly ? void 0 : () => remove(id2),
111
+ removeLabel: "\u79FB\u9664 " + (option?.label || id2)
112
+ },
113
+ id2
114
+ );
115
+ }),
116
+ !value.length && /* @__PURE__ */ jsx("span", { className: s.tagPlaceholder, children: placeholder }),
117
+ /* @__PURE__ */ jsx("span", { className: s.tagChevron, children: /* @__PURE__ */ jsx(DisclosureChevron, { expanded: pop.open }) })
118
+ ]
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx("span", { id: id + "-summary", className: s.hidden, children: value.length ? "\u5DF2\u9009\u62E9 " + summary : "\u672A\u9009\u62E9" })
122
+ ] }) : /* @__PURE__ */ jsx(
60
123
  SelectTrigger,
61
124
  {
62
125
  ...fieldTriggerProps(field),
@@ -88,6 +151,7 @@ function MultiSelect({
88
151
  onClose: pop.close,
89
152
  label,
90
153
  width: 270,
154
+ matchAnchorWidth: display === "tags",
91
155
  onKeyDown: (event) => moveMenuFocus(event, list.current),
92
156
  children: [
93
157
  /* @__PURE__ */ jsxs("div", { className: s.multiSearch, children: [
@@ -8,6 +8,43 @@
8
8
  max-width: 280px;
9
9
  padding: 5px 10px;
10
10
  }
11
+ .tagTrigger {
12
+ composes: surface from "./Control.module.css";
13
+ display: flex;
14
+ flex-wrap: wrap;
15
+ align-items: center;
16
+ gap: 5px;
17
+ width: 100%;
18
+ min-width: 0;
19
+ padding: 5px 8px;
20
+ cursor: pointer;
21
+ }
22
+ .tagTrigger.tagTrigger {
23
+ height: auto;
24
+ min-height: var(--control-height);
25
+ }
26
+ .tagPlaceholder {
27
+ color: var(--placeholder);
28
+ }
29
+ .tagChevron {
30
+ display: flex;
31
+ align-items: center;
32
+ margin-left: auto;
33
+ color: var(--muted);
34
+ }
35
+ .tagChevron > svg {
36
+ width: 16px;
37
+ height: 16px;
38
+ }
39
+ .hidden {
40
+ position: absolute;
41
+ width: 1px;
42
+ height: 1px;
43
+ margin: -1px;
44
+ overflow: hidden;
45
+ clip: rect(0, 0, 0, 0);
46
+ white-space: nowrap;
47
+ }
11
48
  .multiTrigger span {
12
49
  white-space: nowrap;
13
50
  overflow: hidden;
@@ -38,6 +75,7 @@
38
75
  color: var(--muted);
39
76
  }
40
77
  .multiList {
78
+ composes: stack from "./OptionList.module.css";
41
79
  padding: 5px 0;
42
80
  max-height: 220px;
43
81
  overflow: auto;
@@ -40,6 +40,7 @@ function NumberInput({
40
40
  {
41
41
  className: s.number,
42
42
  "data-disabled": disabled,
43
+ "data-readonly": readOnly || void 0,
43
44
  "aria-invalid": field.invalid || invalid,
44
45
  children: [
45
46
  /* @__PURE__ */ jsx(
@@ -0,0 +1,12 @@
1
+ .stack {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--menu-item-spacing, 1px);
5
+ }
6
+ .stack > * {
7
+ flex-shrink: 0;
8
+ }
9
+ .stack.stack > [data-menu-item],
10
+ .stack.stack > * > [data-menu-item] {
11
+ margin: 0;
12
+ }
@@ -20,6 +20,7 @@ function PasswordField({
20
20
  {
21
21
  className: s.control,
22
22
  "data-disabled": field.disabled,
23
+ "data-readonly": field.readOnly || void 0,
23
24
  "aria-invalid": field.invalid,
24
25
  children: [
25
26
  /* @__PURE__ */ jsx(
@@ -61,6 +61,7 @@ function PinInput({
61
61
  ref: root,
62
62
  className: s.root,
63
63
  "data-disabled": field.disabled,
64
+ "data-readonly": field.readOnly || void 0,
64
65
  onPointerDown: (event) => {
65
66
  if (event.button !== 0 || field.disabled) return;
66
67
  event.preventDefault();
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode, type RefObject } from "react";
2
- export declare function Popover({ open: requestedOpen, onClose, anchor, children, width, label, role, id, onKeyDown, descriptionId, }: {
2
+ export declare function Popover({ open: requestedOpen, onClose, anchor, children, width, label, role, id, onKeyDown, descriptionId, autoFocus, matchAnchorWidth, className, }: {
3
3
  open: boolean;
4
4
  onClose(): void;
5
5
  anchor: RefObject<HTMLElement | null>;
@@ -10,4 +10,7 @@ export declare function Popover({ open: requestedOpen, onClose, anchor, children
10
10
  id?: string;
11
11
  onKeyDown?: React.KeyboardEventHandler<HTMLDivElement>;
12
12
  descriptionId?: string;
13
+ autoFocus?: boolean;
14
+ matchAnchorWidth?: boolean;
15
+ className?: string;
13
16
  }): import("react").ReactPortal;
@@ -19,7 +19,10 @@ function Popover({
19
19
  role = "dialog",
20
20
  id,
21
21
  onKeyDown,
22
- descriptionId
22
+ descriptionId,
23
+ autoFocus = true,
24
+ matchAnchorWidth = false,
25
+ className = ""
23
26
  }) {
24
27
  const owner = useSurfacePortal(), portalHost = owner?.host || document.body, open = requestedOpen && owner?.active !== false;
25
28
  const ref = useRef(null), close = useRef(onClose);
@@ -64,7 +67,10 @@ function Popover({
64
67
  const viewportBottom = viewportTop + (viewport?.height ?? innerHeight);
65
68
  const rect = trigger.getBoundingClientRect(), available = Math.max(0, viewportBottom - viewportTop - 24);
66
69
  surface.style.maxHeight = available + "px";
67
- const actualWidth = Math.min(width, Math.max(0, viewportWidth - 24));
70
+ const actualWidth = Math.min(
71
+ matchAnchorWidth ? rect.width : width,
72
+ Math.max(0, viewportWidth - 24)
73
+ );
68
74
  surface.style.width = actualWidth + "px";
69
75
  const height = Math.min(surface.scrollHeight + 2, available);
70
76
  surface.style.left = Math.max(
@@ -86,7 +92,7 @@ function Popover({
86
92
  "enter",
87
93
  surface.dataset.side === "top" ? "top" : "bottom"
88
94
  );
89
- if (role !== "listbox")
95
+ if (autoFocus && role !== "listbox")
90
96
  (surface.querySelector("[data-autofocus]") || surface.querySelector(
91
97
  'input:not([disabled]),button:not([disabled]),[tabindex="0"]'
92
98
  ))?.focus({ preventScroll: true });
@@ -126,7 +132,7 @@ function Popover({
126
132
  window.visualViewport?.removeEventListener("resize", position);
127
133
  window.visualViewport?.removeEventListener("scroll", position);
128
134
  };
129
- }, [open, anchor, width, role, portalHost]);
135
+ }, [open, anchor, width, role, portalHost, autoFocus, matchAnchorWidth]);
130
136
  return createPortal(
131
137
  /* @__PURE__ */ jsx(
132
138
  "div",
@@ -138,7 +144,7 @@ function Popover({
138
144
  "aria-label": label,
139
145
  "aria-describedby": descriptionId,
140
146
  onKeyDown,
141
- className: s.popover,
147
+ className: s.popover + " " + className,
142
148
  children: /* @__PURE__ */ jsx(FieldContext, { value: null, children })
143
149
  }
144
150
  ),
@@ -14,3 +14,6 @@
14
14
  .popover[inert] {
15
15
  pointer-events: none;
16
16
  }
17
+ .popover:not(:popover-open) {
18
+ display: none;
19
+ }
@@ -46,7 +46,7 @@
46
46
  width: 8px;
47
47
  height: 8px;
48
48
  border-radius: 50%;
49
- background: var(--accent);
49
+ background: var(--accent-fill, var(--accent));
50
50
  transform: scale(0.25);
51
51
  opacity: 0;
52
52
  transition:
@@ -54,7 +54,7 @@
54
54
  opacity var(--motion-hover);
55
55
  }
56
56
  .group input:checked + .mark {
57
- border-color: var(--accent);
57
+ border-color: var(--accent-fill, var(--accent));
58
58
  }
59
59
  .group input:checked + .mark::after {
60
60
  transform: scale(1);
@@ -20,6 +20,7 @@ function SearchField({
20
20
  "div",
21
21
  {
22
22
  "data-disabled": props.disabled,
23
+ "data-readonly": props.readOnly || void 0,
23
24
  "aria-busy": loading || void 0,
24
25
  className: `${s.search} ${className}`,
25
26
  children: [
@@ -136,6 +136,7 @@ function Select({
136
136
  ),
137
137
  label,
138
138
  role: "listbox",
139
+ className: s.options,
139
140
  children: [
140
141
  !options.length && /* @__PURE__ */ jsx("p", { className: s.empty, children: emptyText }),
141
142
  options.map((option, index) => /* @__PURE__ */ jsx(
@@ -10,6 +10,9 @@
10
10
  gap: 18px;
11
11
  text-align: left;
12
12
  }
13
+ .options {
14
+ composes: stack from "./OptionList.module.css";
15
+ }
13
16
 
14
17
  .select svg {
15
18
  color: var(--muted);
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function SelectionTag({ label, icon, disabled, onRemove, removeLabel, }: {
3
+ label: string;
4
+ icon?: ReactNode;
5
+ disabled?: boolean;
6
+ onRemove?(): void;
7
+ removeLabel?: string;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { X } from "lucide-react";
3
+ import s from "./TagInput.module.css";
4
+ function SelectionTag({
5
+ label,
6
+ icon,
7
+ disabled,
8
+ onRemove,
9
+ removeLabel = "\u5220\u9664\u6807\u7B7E " + label
10
+ }) {
11
+ return /* @__PURE__ */ jsxs("span", { className: s.tag, children: [
12
+ icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: icon }),
13
+ /* @__PURE__ */ jsx("span", { children: label }),
14
+ onRemove && /* @__PURE__ */ jsx(
15
+ "button",
16
+ {
17
+ type: "button",
18
+ disabled,
19
+ "aria-label": removeLabel,
20
+ onMouseDown: (event) => event.preventDefault(),
21
+ onClick: (event) => {
22
+ event.stopPropagation();
23
+ onRemove();
24
+ },
25
+ onKeyDown: (event) => {
26
+ if (event.key === "Backspace" || event.key === "Delete") {
27
+ event.preventDefault();
28
+ event.stopPropagation();
29
+ onRemove();
30
+ }
31
+ },
32
+ children: /* @__PURE__ */ jsx(X, {})
33
+ }
34
+ )
35
+ ] });
36
+ }
37
+ export {
38
+ SelectionTag
39
+ };
@@ -1,8 +1,10 @@
1
1
  import type { ReactNode } from "react";
2
- export declare function SettingsLayout({ value, onChange, children, }: {
2
+ import { type IndicatorGroup } from "./Indicator";
3
+ export declare function SettingsLayout({ value, onChange, children, groups, }: {
3
4
  value: string;
4
5
  onChange(value: string): void;
5
6
  children: ReactNode;
7
+ groups?: IndicatorGroup<string>[];
6
8
  }): import("react").JSX.Element;
7
9
  export declare function SettingsCard({ children }: {
8
10
  children: ReactNode;
@@ -1,20 +1,22 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Settings2, Palette } from "lucide-react";
3
3
  import { Indicator } from "./Indicator.js";
4
+ import { Select } from "./Select.js";
4
5
  import s from "./SettingsLayout.module.css";
5
6
  function SettingsLayout({
6
7
  value,
7
8
  onChange,
8
- children
9
+ children,
10
+ groups
9
11
  }) {
10
- return /* @__PURE__ */ jsxs("div", { className: s.layout, children: [
12
+ return /* @__PURE__ */ jsxs("div", { className: s.layout, "data-grouped": groups ? true : void 0, children: [
11
13
  /* @__PURE__ */ jsx(
12
14
  Indicator,
13
15
  {
14
16
  label: "\u8BBE\u7F6E\u5206\u7C7B",
15
17
  value,
16
18
  onChange,
17
- groups: [
19
+ groups: groups ?? [
18
20
  {
19
21
  id: "settings",
20
22
  items: [
@@ -33,6 +35,20 @@ function SettingsLayout({
33
35
  ]
34
36
  }
35
37
  ),
38
+ groups && /* @__PURE__ */ jsx("div", { className: s.mobileCategories, children: /* @__PURE__ */ jsx(
39
+ Select,
40
+ {
41
+ label: "\u8BBE\u7F6E\u5206\u7C7B",
42
+ value,
43
+ onChange,
44
+ options: groups.flatMap(
45
+ (group) => group.items.map((item) => ({
46
+ value: item.value,
47
+ label: (group.label ? group.label + " \xB7 " : "") + item.label
48
+ }))
49
+ )
50
+ }
51
+ ) }),
36
52
  /* @__PURE__ */ jsx("div", { className: s.panel, children })
37
53
  ] });
38
54
  }
@@ -8,6 +8,13 @@
8
8
  position: sticky;
9
9
  top: 0;
10
10
  }
11
+ .layout[data-grouped] {
12
+ grid-template-columns: 164px minmax(0, 1fr);
13
+ }
14
+ .mobileCategories {
15
+ display: none;
16
+ min-width: 0;
17
+ }
11
18
  .panel {
12
19
  min-width: 0;
13
20
  }
@@ -21,12 +28,25 @@
21
28
  border: 1px solid var(--line);
22
29
  border-radius: var(--radius);
23
30
  background: var(--surface);
31
+ box-shadow: var(--card-shadow, none);
24
32
  margin-bottom: 28px;
25
33
  }
26
34
  .card > :last-child {
27
35
  border-bottom: 0;
28
36
  }
29
37
  @media (max-width: 800px) {
38
+ .layout[data-grouped] {
39
+ grid-template-columns: minmax(0, 1fr);
40
+ }
41
+ .layout[data-grouped] > nav {
42
+ display: none;
43
+ }
44
+ .mobileCategories {
45
+ display: block;
46
+ }
47
+ .mobileCategories > button {
48
+ width: 100%;
49
+ }
30
50
  .layout {
31
51
  grid-template-columns: 1fr;
32
52
  gap: 24px;
@@ -30,16 +30,19 @@
30
30
  border-color: var(--muted);
31
31
  }
32
32
  .switch[aria-checked="true"] {
33
- background: var(--accent);
34
- border-color: var(--accent);
33
+ background: var(--switch-on, var(--accent));
34
+ border-color: var(--switch-on, var(--accent));
35
35
  }
36
36
  .switch[aria-checked="true"]:hover:not(:disabled) {
37
- background: color-mix(in srgb, var(--accent) 88%, var(--fg));
38
- border-color: var(--accent);
37
+ background: var(
38
+ --switch-on-hover,
39
+ color-mix(in srgb, var(--accent) 88%, var(--fg))
40
+ );
41
+ border-color: var(--switch-on, var(--accent));
39
42
  }
40
43
  .switch[aria-checked="true"] span {
41
44
  transform: translateX(14px);
42
- background: var(--on-accent);
45
+ background: var(--switch-thumb-on, var(--on-accent));
43
46
  }
44
47
  .switch:active:not(:disabled) span {
45
48
  width: 17px;
@@ -1,12 +1,16 @@
1
- import type { ReactNode } from "react";
1
+ import type { ReactNode, HTMLAttributes, CSSProperties } from "react";
2
2
  export interface Column<T> {
3
3
  key: string;
4
4
  title: ReactNode;
5
5
  render: (row: T) => ReactNode;
6
+ align?: "left" | "center" | "right";
7
+ width?: CSSProperties["width"];
6
8
  }
7
- export declare function Table<T>({ columns, rows, rowKey, label, }: {
9
+ export declare function Table<T>({ columns, rows, rowKey, label, role, rowAttributes, }: {
8
10
  columns: Column<T>[];
9
11
  rows: T[];
10
12
  rowKey: (row: T) => string;
11
13
  label: string;
14
+ role?: "table" | "treegrid";
15
+ rowAttributes?(row: T): HTMLAttributes<HTMLTableRowElement>;
12
16
  }): import("react").JSX.Element;
@@ -4,13 +4,23 @@ function Table({
4
4
  columns,
5
5
  rows,
6
6
  rowKey,
7
- label
7
+ label,
8
+ role,
9
+ rowAttributes
8
10
  }) {
9
- return /* @__PURE__ */ jsx("div", { className: s.scroll, role: "region", "aria-label": label, tabIndex: 0, children: /* @__PURE__ */ jsxs("table", { className: s.table, children: [
11
+ return /* @__PURE__ */ jsx("div", { className: s.scroll, role: "region", "aria-label": label, tabIndex: 0, children: /* @__PURE__ */ jsxs("table", { className: s.table, role, children: [
10
12
  /* @__PURE__ */ jsx("caption", { className: s.caption, children: label }),
11
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((c) => /* @__PURE__ */ jsx("th", { scope: "col", children: c.title }, c.key)) }) }),
13
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((c) => /* @__PURE__ */ jsx(
14
+ "th",
15
+ {
16
+ scope: "col",
17
+ style: { textAlign: c.align, width: c.width },
18
+ children: c.title
19
+ },
20
+ c.key
21
+ )) }) }),
12
22
  /* @__PURE__ */ jsxs("tbody", { children: [
13
- rows.map((r) => /* @__PURE__ */ jsx("tr", { children: columns.map((c) => /* @__PURE__ */ jsx("td", { children: c.render(r) }, c.key)) }, rowKey(r))),
23
+ rows.map((r) => /* @__PURE__ */ jsx("tr", { ...rowAttributes?.(r), children: columns.map((c) => /* @__PURE__ */ jsx("td", { style: { textAlign: c.align }, children: c.render(r) }, c.key)) }, rowKey(r))),
14
24
  rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: s.empty, children: "\u6682\u65E0\u7ED3\u679C" }) })
15
25
  ] })
16
26
  ] }) });
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState } from "react";
3
- import { X } from "lucide-react";
3
+ import { SelectionTag } from "./SelectionTag.js";
4
4
  import { FormField } from "./FormField.js";
5
5
  import { joinIds, useFieldControl } from "./field.js";
6
6
  import s from "./TagInput.module.css";
@@ -63,31 +63,19 @@ function TagInput({
63
63
  ref: root,
64
64
  className: s.control,
65
65
  "data-disabled": field.disabled,
66
+ "data-readonly": field.readOnly || void 0,
66
67
  "aria-invalid": field.invalid || !!error,
67
68
  onClick: () => input.current?.focus(),
68
69
  children: [
69
- value.map((tag, index) => /* @__PURE__ */ jsxs("span", { className: s.tag, children: [
70
- /* @__PURE__ */ jsx("span", { children: tag }),
71
- !field.readOnly && /* @__PURE__ */ jsx(
72
- "button",
73
- {
74
- type: "button",
75
- disabled: field.disabled,
76
- "aria-label": "\u5220\u9664\u6807\u7B7E " + tag,
77
- onClick: (event) => {
78
- event.stopPropagation();
79
- remove(index);
80
- },
81
- onKeyDown: (event) => {
82
- if (event.key === "Backspace" || event.key === "Delete") {
83
- event.preventDefault();
84
- remove(index);
85
- }
86
- },
87
- children: /* @__PURE__ */ jsx(X, {})
88
- }
89
- )
90
- ] }, tag)),
70
+ value.map((tag, index) => /* @__PURE__ */ jsx(
71
+ SelectionTag,
72
+ {
73
+ label: tag,
74
+ disabled: field.disabled,
75
+ onRemove: field.readOnly ? void 0 : () => remove(index)
76
+ },
77
+ tag
78
+ )),
91
79
  /* @__PURE__ */ jsx(
92
80
  "input",
93
81
  {
@@ -49,7 +49,7 @@
49
49
  border-radius: 3px;
50
50
  color: var(--muted);
51
51
  }
52
- .tag button:hover {
52
+ .tag button:hover:not(:disabled) {
53
53
  background: var(--hover);
54
54
  color: var(--fg);
55
55
  }
@@ -1,7 +1,10 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useLayoutEffect, useRef } from "react";
3
3
  import { UIProvider } from "./UIProvider.js";
4
- import { appearanceDefaults } from "./appearance.js";
4
+ import {
5
+ appearanceDefaults,
6
+ normalizeAppearance
7
+ } from "./appearance.js";
5
8
  function ThemeProvider({
6
9
  theme: suppliedTheme = "minimal",
7
10
  mode: suppliedMode = "system",
@@ -17,7 +20,10 @@ function ThemeProvider({
17
20
  const dark = mode === "system" ? media.matches : mode === "dark";
18
21
  root.dataset.uiTheme = theme;
19
22
  root.dataset.theme = dark ? "dark" : "light";
20
- const prefs = appearance ?? appearanceDefaults(theme), baseline = appearanceDefaults(theme);
23
+ const prefs = normalizeAppearance(
24
+ appearance ?? appearanceDefaults(theme),
25
+ theme
26
+ ), baseline = appearanceDefaults(theme);
21
27
  const palette = [
22
28
  theme,
23
29
  dark,
@@ -41,13 +47,21 @@ function ThemeProvider({
41
47
  delete root.dataset.radius;
42
48
  const rgb = prefs.accent.slice(1).match(/../g).map((v) => parseInt(v, 16));
43
49
  const customAccent = prefs.accent.toLowerCase() !== baseline.accent.toLowerCase();
50
+ const onAccent = rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 > 155 ? "#141414" : "#fff";
44
51
  const background = dark ? "darkBackground" : "lightBackground", foreground = dark ? "darkForeground" : "lightForeground";
45
52
  const overrides = {
46
53
  "--surface": prefs[background] !== baseline[background] ? prefs[background] : void 0,
47
54
  "--fg": prefs[foreground] !== baseline[foreground] ? prefs[foreground] : void 0,
48
55
  "--text": prefs[foreground] !== baseline[foreground] ? prefs[foreground] : void 0,
49
56
  "--accent": customAccent ? prefs.accent : void 0,
50
- "--on-accent": customAccent ? rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 > 155 ? "#141414" : "#fff" : void 0,
57
+ "--on-accent": customAccent ? onAccent : void 0,
58
+ "--accent-soft": customAccent ? `color-mix(in srgb, ${prefs.accent} 12%, var(--surface))` : void 0,
59
+ "--accent-fill": customAccent ? prefs.accent : void 0,
60
+ "--accent-fill-hover": customAccent ? `color-mix(in srgb, ${prefs.accent} 86%, var(--surface))` : void 0,
61
+ "--on-accent-fill": customAccent ? onAccent : void 0,
62
+ "--switch-on": customAccent ? prefs.accent : void 0,
63
+ "--switch-on-hover": customAccent ? `color-mix(in srgb, ${prefs.accent} 88%, var(--fg))` : void 0,
64
+ "--switch-thumb-on": customAccent ? onAccent : void 0,
51
65
  "--radius": prefs.radius !== baseline.radius ? prefs.radius + "px" : void 0,
52
66
  "--radius-lg": prefs.radius !== baseline.radius ? "calc(var(--radius) + 4px)" : void 0,
53
67
  "--ui-font": prefs.font === "system" ? void 0 : '"' + prefs.font + '", sans-serif'