@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,12 +1,26 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useId, useRef, useState } from "react";
2
+ import {
3
+ useCallback,
4
+ useEffect,
5
+ useId,
6
+ useRef,
7
+ useState
8
+ } from "react";
3
9
  import { Plus } from "lucide-react";
4
- import { fieldTriggerProps, useFieldControl } from "./field.js";
10
+ import {
11
+ FieldContext,
12
+ fieldInputProps,
13
+ fieldTriggerProps,
14
+ useFieldControl
15
+ } from "./field.js";
5
16
  import { SelectTrigger } from "./SelectTrigger.js";
6
17
  import { Popover } from "./Popover.js";
7
18
  import { SearchField } from "./SearchField.js";
19
+ import { Input } from "./Input.js";
8
20
  import { MenuItem } from "./MenuItem.js";
9
21
  import { Spinner } from "./Spinner.js";
22
+ import { Button } from "./Button.js";
23
+ import { DisclosureChevron } from "./DisclosureChevron.js";
10
24
  import s from "./Combobox.module.css";
11
25
  function Combobox({
12
26
  value,
@@ -16,14 +30,24 @@ function Combobox({
16
30
  onCreate,
17
31
  label = "\u641C\u7D22\u9009\u62E9",
18
32
  placeholder = "\u8BF7\u9009\u62E9",
33
+ mode = "panel",
19
34
  ...props
20
35
  }) {
21
- const field = useFieldControl({ ...props, label }), anchor = useRef(null), list = useRef(null), id = useId(), [open, setOpen] = useState(false), [query, setQuery] = useState(""), [remote, setRemote] = useState([]), [loading, setLoading] = useState(false), [error, setError] = useState(""), [active, setActive] = useState(0), [created, setCreated] = useState(), [chosen, setChosen] = useState(), [creating, setCreating] = useState(false), [retry, setRetry] = useState(0);
22
- const generation = useRef(0);
36
+ const field = useFieldControl({ ...props, label }), anchor = useRef(null), list = useRef(null), id = useId();
37
+ const [open, setOpen] = useState(false), [query, setQuery] = useState(""), [editing, setEditing] = useState(false), [remote, setRemote] = useState([]), [loading, setLoading] = useState(false), [error, setError] = useState(""), [failure, setFailure] = useState("load"), [active, setActive] = useState(-1), [created, setCreated] = useState(), [chosen, setChosen] = useState(), [creating, setCreating] = useState(false), [retry, setRetry] = useState(0);
38
+ const generation = useRef(0), restoring = useRef(false), loader = useRef(loadOptions);
39
+ loader.current = loadOptions;
40
+ const inline = mode === "input", async = !!loadOptions;
23
41
  const close = useCallback(() => {
24
42
  generation.current++;
25
43
  setOpen(false);
44
+ setEditing(false);
26
45
  setCreating(false);
46
+ setLoading(false);
47
+ restoring.current = true;
48
+ queueMicrotask(() => {
49
+ restoring.current = false;
50
+ });
27
51
  }, []);
28
52
  useEffect(
29
53
  () => () => {
@@ -31,28 +55,21 @@ function Combobox({
31
55
  },
32
56
  []
33
57
  );
34
- const loader = useRef(loadOptions);
35
- loader.current = loadOptions;
36
- const async = !!loadOptions;
37
58
  useEffect(() => {
38
59
  if (!open || !async) return;
39
- const controller = new AbortController();
60
+ const controller = new AbortController(), revision = generation.current;
40
61
  setLoading(true);
41
62
  setError("");
42
63
  const timer = setTimeout(() => {
43
64
  Promise.resolve().then(() => loader.current(query, controller.signal)).then((items) => {
44
- if (!controller.signal.aborted) {
65
+ if (!controller.signal.aborted && revision === generation.current) {
45
66
  setRemote(items);
46
67
  setLoading(false);
47
- setActive(
48
- Math.max(
49
- 0,
50
- items.findIndex((item) => !item.disabled)
51
- )
52
- );
68
+ setActive(inline ? -1 : items.findIndex((item) => !item.disabled));
53
69
  }
54
70
  }).catch(() => {
55
- if (!controller.signal.aborted) {
71
+ if (!controller.signal.aborted && revision === generation.current) {
72
+ setFailure("load");
56
73
  setError("\u9009\u9879\u52A0\u8F7D\u5931\u8D25");
57
74
  setLoading(false);
58
75
  }
@@ -62,10 +79,10 @@ function Combobox({
62
79
  clearTimeout(timer);
63
80
  controller.abort();
64
81
  };
65
- }, [open, query, async, retry]);
82
+ }, [open, query, async, retry, inline]);
66
83
  useEffect(() => {
67
84
  if (field.disabled || field.readOnly) close();
68
- }, [field.disabled, field.readOnly]);
85
+ }, [field.disabled, field.readOnly, close]);
69
86
  const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean), filtered = async ? remote : options.filter(
70
87
  (item) => terms.every(
71
88
  (term) => (item.label + " " + (item.description || "") + " " + item.value).toLowerCase().includes(term)
@@ -73,7 +90,8 @@ function Combobox({
73
90
  );
74
91
  const selected = [...options, ...remote, created, chosen].find(
75
92
  (item) => item?.value === value
76
- );
93
+ ), first = filtered.findIndex((item) => !item.disabled);
94
+ const activeIndex = active >= 0 && filtered[active] && !filtered[active].disabled ? active : inline ? -1 : first;
77
95
  const choose = (item) => {
78
96
  if (item.disabled || field.disabled || field.readOnly) return;
79
97
  setChosen(item);
@@ -82,19 +100,116 @@ function Combobox({
82
100
  anchor.current?.focus();
83
101
  };
84
102
  const show = () => {
103
+ if (field.disabled || field.readOnly || creating) return;
104
+ generation.current++;
105
+ setLoading(async);
85
106
  setQuery("");
86
- setActive(0);
107
+ setEditing(false);
108
+ setActive(inline ? -1 : 0);
109
+ setError("");
110
+ setOpen(true);
111
+ };
112
+ const search = (text) => {
113
+ if (field.disabled || field.readOnly || creating) return;
114
+ generation.current++;
115
+ setLoading(async);
116
+ setQuery(text);
117
+ setEditing(true);
118
+ setActive(inline ? -1 : 0);
87
119
  setError("");
88
120
  setOpen(true);
89
121
  };
90
122
  useEffect(() => {
91
123
  list.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
92
- }, [active, query]);
124
+ }, [activeIndex, query, loading]);
93
125
  const canCreate = !!onCreate && !!query.trim() && !filtered.some(
94
126
  (item) => item.label.toLowerCase() === query.trim().toLowerCase()
95
127
  );
128
+ const create = async () => {
129
+ if (!onCreate || creating || loading || !canCreate) return;
130
+ const revision = generation.current, text = query.trim();
131
+ setCreating(true);
132
+ setError("");
133
+ try {
134
+ const result = await onCreate(text);
135
+ if (revision !== generation.current) return;
136
+ const item = { value: result || text, label: text };
137
+ setCreated(item);
138
+ choose(item);
139
+ } catch {
140
+ if (revision === generation.current) {
141
+ setFailure("create");
142
+ setError("\u521B\u5EFA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
143
+ }
144
+ } finally {
145
+ if (revision === generation.current) setCreating(false);
146
+ }
147
+ };
148
+ const navigate = (event) => {
149
+ if (event.nativeEvent.isComposing || event.keyCode === 229 || field.readOnly || field.disabled)
150
+ return;
151
+ if (event.key === "Escape" && open) {
152
+ event.preventDefault();
153
+ event.stopPropagation();
154
+ close();
155
+ anchor.current?.focus();
156
+ return;
157
+ }
158
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
159
+ event.preventDefault();
160
+ if (!open) {
161
+ show();
162
+ return;
163
+ }
164
+ const direction = event.key === "ArrowDown" ? 1 : -1, start = activeIndex < 0 ? direction === 1 ? -1 : 0 : activeIndex;
165
+ for (let step = 1; step <= filtered.length; step++) {
166
+ const next = (start + direction * step + filtered.length * 2) % filtered.length;
167
+ if (!filtered[next].disabled) {
168
+ setActive(next);
169
+ break;
170
+ }
171
+ }
172
+ } else if (event.key === "Enter" && open) {
173
+ event.preventDefault();
174
+ if (!loading && !creating && !error) {
175
+ const item = filtered[activeIndex < 0 ? first : activeIndex];
176
+ if (item) choose(item);
177
+ else void create();
178
+ }
179
+ }
180
+ };
181
+ const activeId = open && !loading && !error && activeIndex >= 0 ? id + "-" + activeIndex : void 0;
96
182
  return /* @__PURE__ */ jsxs(Fragment, { children: [
97
- /* @__PURE__ */ jsx(
183
+ inline ? /* @__PURE__ */ jsx(FieldContext, { value: { ...field, name: void 0 }, children: /* @__PURE__ */ jsx(
184
+ Input,
185
+ {
186
+ ...fieldInputProps(field),
187
+ name: void 0,
188
+ ref: anchor,
189
+ role: "combobox",
190
+ "aria-label": field.label,
191
+ "aria-expanded": open,
192
+ "aria-busy": loading || creating,
193
+ "aria-controls": id + "-list",
194
+ "aria-autocomplete": "list",
195
+ "aria-activedescendant": activeId,
196
+ autoComplete: "off",
197
+ disabled: field.disabled || creating,
198
+ className: s.inputTrigger,
199
+ value: open && editing ? query : selected?.label || value,
200
+ placeholder,
201
+ leading: selected?.icon,
202
+ trailing: loading || creating ? /* @__PURE__ */ jsx(Spinner, { size: 16, decorative: true }) : /* @__PURE__ */ jsx(DisclosureChevron, { expanded: open }),
203
+ onChange: (event) => search(event.target.value),
204
+ onFocus: () => {
205
+ if (!restoring.current && !open) show();
206
+ },
207
+ onClick: () => {
208
+ if (!open) show();
209
+ },
210
+ onKeyDown: navigate
211
+ }
212
+ ) }) : /* @__PURE__ */ jsx(
98
213
  SelectTrigger,
99
214
  {
100
215
  ...fieldTriggerProps(field),
@@ -107,12 +222,7 @@ function Combobox({
107
222
  onClick: () => {
108
223
  if (!field.readOnly) open ? close() : show();
109
224
  },
110
- onKeyDown: (event) => {
111
- if (!field.readOnly && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
112
- event.preventDefault();
113
- show();
114
- }
115
- },
225
+ onKeyDown: navigate,
116
226
  children: /* @__PURE__ */ jsx("span", { children: selected?.label || value || placeholder })
117
227
  }
118
228
  ),
@@ -134,36 +244,21 @@ function Combobox({
134
244
  anchor,
135
245
  label: field.label + "\u9009\u9879",
136
246
  width: 300,
247
+ autoFocus: !inline,
248
+ matchAnchorWidth: inline,
137
249
  children: /* @__PURE__ */ jsxs("div", { className: s.panel, children: [
138
- /* @__PURE__ */ jsx(
250
+ !inline && /* @__PURE__ */ jsx(
139
251
  SearchField,
140
252
  {
141
253
  label: "\u641C\u7D22" + field.label,
142
254
  placeholder: "\u641C\u7D22\u9009\u9879\u2026",
143
255
  value: query,
144
- onChange: (value2) => {
145
- setQuery(value2);
146
- setActive(0);
147
- },
256
+ onChange: search,
148
257
  loading,
258
+ disabled: creating,
149
259
  "aria-controls": id + "-list",
150
- "aria-activedescendant": filtered[active] ? id + "-" + active : void 0,
151
- onKeyDown: (event) => {
152
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
153
- event.preventDefault();
154
- const direction = event.key === "ArrowDown" ? 1 : -1;
155
- for (let step = 1; step <= filtered.length; step++) {
156
- const next = (active + direction * step + filtered.length * 2) % filtered.length;
157
- if (!filtered[next].disabled) {
158
- setActive(next);
159
- break;
160
- }
161
- }
162
- } else if (event.key === "Enter") {
163
- event.preventDefault();
164
- if (!loading && filtered[active]) choose(filtered[active]);
165
- }
166
- }
260
+ "aria-activedescendant": activeId,
261
+ onKeyDown: navigate
167
262
  }
168
263
  ),
169
264
  /* @__PURE__ */ jsx(
@@ -175,13 +270,22 @@ function Combobox({
175
270
  "aria-label": field.label,
176
271
  className: s.list,
177
272
  "aria-busy": loading,
178
- children: error ? /* @__PURE__ */ jsxs("div", { className: s.empty, children: [
273
+ children: error ? /* @__PURE__ */ jsxs("div", { className: s.empty, role: "alert", children: [
179
274
  /* @__PURE__ */ jsx("span", { children: error }),
180
275
  /* @__PURE__ */ jsx(
181
- "button",
276
+ Button,
182
277
  {
183
- type: "button",
184
- onClick: () => setRetry((value2) => value2 + 1),
278
+ size: "small",
279
+ variant: "ghost",
280
+ onClick: () => {
281
+ setError("");
282
+ if (failure === "create") void create();
283
+ else {
284
+ generation.current++;
285
+ setLoading(true);
286
+ setRetry((value2) => value2 + 1);
287
+ }
288
+ },
185
289
  children: "\u91CD\u8BD5"
186
290
  }
187
291
  )
@@ -196,9 +300,9 @@ function Combobox({
196
300
  icon: item.icon,
197
301
  description: item.description,
198
302
  shortcut: item.shortcut,
199
- reserveIcon: filtered.some((item2) => !!item2.icon),
303
+ reserveIcon: filtered.some((option) => !!option.icon),
200
304
  selected: value === item.value,
201
- active: active === index,
305
+ active: activeIndex === index,
202
306
  disabled: item.disabled,
203
307
  "aria-selected": value === item.value,
204
308
  selection: "check",
@@ -220,26 +324,8 @@ function Combobox({
220
324
  label: "\u521B\u5EFA\u201C" + query.trim() + "\u201D",
221
325
  icon: /* @__PURE__ */ jsx(Plus, {}),
222
326
  disabled: creating,
223
- onClick: async () => {
224
- const revision = generation.current;
225
- setCreating(true);
226
- setError("");
227
- try {
228
- const result = await onCreate(query.trim());
229
- if (revision !== generation.current) return;
230
- const item = {
231
- value: result || query.trim(),
232
- label: query.trim()
233
- };
234
- setCreated(item);
235
- choose(item);
236
- } catch {
237
- if (revision === generation.current)
238
- setError("\u521B\u5EFA\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002");
239
- } finally {
240
- if (revision === generation.current) setCreating(false);
241
- }
242
- }
327
+ onMouseDown: (event) => event.preventDefault(),
328
+ onClick: () => void create()
243
329
  }
244
330
  )
245
331
  ] })
@@ -3,7 +3,12 @@
3
3
  flex-direction: column;
4
4
  gap: 7px;
5
5
  }
6
+ .inputTrigger {
7
+ min-width: 0;
8
+ width: 100%;
9
+ }
6
10
  .list {
11
+ composes: stack from "./OptionList.module.css";
7
12
  max-height: 240px;
8
13
  min-height: 40px;
9
14
  overflow: auto;
@@ -17,10 +22,3 @@
17
22
  color: var(--muted);
18
23
  font-size: 12px;
19
24
  }
20
- .empty button {
21
- border: 0;
22
- background: var(--hover);
23
- border-radius: 5px;
24
- padding: 4px 8px;
25
- color: var(--fg);
26
- }
@@ -13,11 +13,24 @@
13
13
  box-shadow var(--motion-hover),
14
14
  background-color var(--motion-hover);
15
15
  }
16
- .surface:hover:where(:not(:disabled):not([data-disabled="true"])) {
17
- border-color: color-mix(
18
- in srgb,
19
- var(--fg) var(--field-hover-strength),
20
- var(--surface)
16
+ .surface:hover:where(
17
+ :not(
18
+ :disabled,
19
+ [data-disabled="true"],
20
+ [aria-disabled="true"],
21
+ [readonly],
22
+ [aria-readonly="true"],
23
+ [data-readonly="true"],
24
+ [data-disabled="true"] *,
25
+ [aria-disabled="true"] *,
26
+ [data-readonly="true"] *,
27
+ fieldset:disabled *,
28
+ :has(input:disabled, textarea:disabled)
29
+ )
30
+ ) {
31
+ border-color: var(
32
+ --field-hover-border,
33
+ color-mix(in srgb, var(--fg) var(--field-hover-strength), var(--surface))
21
34
  );
22
35
  }
23
36
  .surface[data-focus="true"],
@@ -8,6 +8,9 @@ export type SingleDatePickerProps = FieldOptions & DateConstraints & {
8
8
  label: string;
9
9
  mode?: Exclude<DatePickerMode, "range">;
10
10
  seconds?: boolean;
11
+ timeZone?: string;
12
+ clearable?: boolean;
13
+ placeholder?: string;
11
14
  };
12
15
  export type DatePickerProps = SingleDatePickerProps | (DateRangePickerProps & {
13
16
  mode: "range";
@@ -5,7 +5,7 @@ import { useId, useState } from "react";
5
5
  import { CalendarDays, Clock3 } from "lucide-react";
6
6
  import { Popover } from "./Popover.js";
7
7
  import { usePopover } from "./usePopover.js";
8
- import { Calendar, dateKey } from "./Calendar.js";
8
+ import { Calendar } from "./Calendar.js";
9
9
  import { TimeColumns } from "./TimeColumns.js";
10
10
  import { Button } from "./Button.js";
11
11
  import s from "./DatePicker.module.css";
@@ -14,6 +14,7 @@ import {
14
14
  isDateAllowed
15
15
  } from "./dateConstraints.js";
16
16
  import { DateRangePicker } from "./DateRangePicker.js";
17
+ import { calendarNow } from "./calendarNow.js";
17
18
  function DatePicker(props) {
18
19
  return props.mode === "range" ? /* @__PURE__ */ jsx(DateRangePicker, { ...props }) : /* @__PURE__ */ jsx(SingleDatePicker, { ...props });
19
20
  }
@@ -23,6 +24,9 @@ function SingleDatePicker({
23
24
  label,
24
25
  mode = "date",
25
26
  seconds = false,
27
+ timeZone,
28
+ clearable = false,
29
+ placeholder = "\u8BF7\u9009\u62E9",
26
30
  disabled,
27
31
  readOnly,
28
32
  name,
@@ -47,7 +51,8 @@ function SingleDatePicker({
47
51
  hint,
48
52
  ...aria
49
53
  });
50
- const pop = usePopover(field.disabled || field.readOnly), id = useId(), [date, setDate] = useState(value.slice(0, 10) || dateKey(/* @__PURE__ */ new Date())), [time, setTime] = useState("00:00");
54
+ const now = () => calendarNow(timeZone), today = now().date;
55
+ const pop = usePopover(field.disabled || field.readOnly), id = useId(), [date, setDate] = useState(value.slice(0, 10) || today), [time, setTime] = useState("00:00");
51
56
  const close = pop.closeAndFocus;
52
57
  const show = () => {
53
58
  if (field.disabled || field.readOnly) return;
@@ -56,7 +61,7 @@ function SingleDatePicker({
56
61
  return;
57
62
  }
58
63
  setDate(
59
- mode === "time" ? dateKey(/* @__PURE__ */ new Date()) : boundedDate(value.slice(0, 10), constraints)
64
+ mode === "time" ? now().date : boundedDate(value.slice(0, 10) || now().date, constraints)
60
65
  );
61
66
  setTime(
62
67
  (mode === "time" ? value : value.split(/[T ]/)[1]) || (seconds ? "00:00:00" : "00:00")
@@ -82,6 +87,7 @@ function SingleDatePicker({
82
87
  "aria-expanded": pop.open,
83
88
  "aria-haspopup": "dialog",
84
89
  "aria-controls": id,
90
+ "data-placeholder": !value || void 0,
85
91
  onClick: show,
86
92
  onKeyDown: (e) => {
87
93
  if (e.key === "ArrowDown" && !pop.open) {
@@ -90,7 +96,7 @@ function SingleDatePicker({
90
96
  }
91
97
  },
92
98
  children: [
93
- /* @__PURE__ */ jsx("span", { children: value.replace("T", " ") || "\u8BF7\u9009\u62E9" }),
99
+ /* @__PURE__ */ jsx("span", { children: value.replace("T", " ") || placeholder }),
94
100
  mode === "time" ? /* @__PURE__ */ jsx(Clock3, {}) : /* @__PURE__ */ jsx(CalendarDays, {})
95
101
  ]
96
102
  }
@@ -119,6 +125,7 @@ function SingleDatePicker({
119
125
  Calendar,
120
126
  {
121
127
  ...constraints,
128
+ today,
122
129
  key: pop.open ? "open" : "closed",
123
130
  value: date,
124
131
  onChange: (next) => {
@@ -143,16 +150,14 @@ function SingleDatePicker({
143
150
  ] }),
144
151
  /* @__PURE__ */ jsxs("footer", { className: s.footer, children: [
145
152
  /* @__PURE__ */ jsx(
146
- "button",
153
+ Button,
147
154
  {
155
+ variant: "ghost",
156
+ size: "small",
148
157
  type: "button",
149
- disabled: mode !== "time" && !isDateAllowed(dateKey(/* @__PURE__ */ new Date()), constraints),
158
+ disabled: mode !== "time" && !isDateAllowed(today, constraints),
150
159
  onClick: () => {
151
- const now = /* @__PURE__ */ new Date(), nextDate = dateKey(now), nextTime = [
152
- now.getHours(),
153
- now.getMinutes(),
154
- ...seconds ? [now.getSeconds()] : []
155
- ].map((v) => String(v).padStart(2, "0")).join(":");
160
+ const current = now(), nextDate = current.date, nextTime = current.time.slice(0, seconds ? 8 : 5);
156
161
  if (mode !== "time" && !isDateAllowed(nextDate, constraints))
157
162
  return;
158
163
  setDate(nextDate);
@@ -165,16 +170,31 @@ function SingleDatePicker({
165
170
  children: mode === "date" ? "\u4ECA\u5929" : "\u6B64\u523B"
166
171
  }
167
172
  ),
168
- mode !== "date" && /* @__PURE__ */ jsx(
169
- Button,
170
- {
171
- variant: "primary",
172
- size: "small",
173
- onClick: commit,
174
- disabled: mode !== "time" && !isDateAllowed(date, constraints),
175
- children: "\u786E\u5B9A"
176
- }
177
- )
173
+ (clearable || mode !== "date") && /* @__PURE__ */ jsxs("div", { className: s.rangeActions, children: [
174
+ clearable && /* @__PURE__ */ jsx(
175
+ Button,
176
+ {
177
+ size: "small",
178
+ variant: "ghost",
179
+ disabled: !value,
180
+ onClick: () => {
181
+ onChange("");
182
+ close();
183
+ },
184
+ children: "\u6E05\u7A7A"
185
+ }
186
+ ),
187
+ mode !== "date" && /* @__PURE__ */ jsx(
188
+ Button,
189
+ {
190
+ variant: "primary",
191
+ size: "small",
192
+ onClick: commit,
193
+ disabled: mode !== "time" && !isDateAllowed(date, constraints),
194
+ children: "\u786E\u5B9A"
195
+ }
196
+ )
197
+ ] })
178
198
  ] })
179
199
  ]
180
200
  }
@@ -12,6 +12,9 @@
12
12
  height: 15px;
13
13
  color: var(--muted);
14
14
  }
15
+ .trigger[data-placeholder="true"] > span {
16
+ color: var(--placeholder);
17
+ }
15
18
  .body {
16
19
  display: flex;
17
20
  }
@@ -1,9 +1,14 @@
1
1
  import { type ReactNode } from "react";
2
- export declare function Dialog({ open: requestedOpen, onClose, title, children, footer, wide, }: {
2
+ export declare function Dialog({ open: requestedOpen, onClose, title, children, footer, wide, size, role, descriptionId, busy, variant, }: {
3
3
  open: boolean;
4
4
  onClose(): void;
5
5
  title: string;
6
6
  children: ReactNode;
7
7
  footer?: ReactNode;
8
8
  wide?: boolean;
9
+ size?: "small" | "medium" | "large";
10
+ role?: "dialog" | "alertdialog";
11
+ descriptionId?: string;
12
+ busy?: boolean;
13
+ variant?: "default" | "message" | "confirm" | "form";
9
14
  }): import("react").ReactPortal;
@@ -24,7 +24,12 @@ function Dialog({
24
24
  title,
25
25
  children,
26
26
  footer,
27
- wide = false
27
+ wide = false,
28
+ size = "medium",
29
+ role = "dialog",
30
+ descriptionId,
31
+ busy = false,
32
+ variant = "default"
28
33
  }) {
29
34
  const owner = useSurfacePortal(), open = requestedOpen && owner?.active !== false;
30
35
  const { ref, host, setRef } = useSurfaceHost();
@@ -50,8 +55,12 @@ function Dialog({
50
55
  previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
51
56
  element.showModal();
52
57
  element.querySelector(
53
- 'input:not([disabled]):not([readonly]):not([type="hidden"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]),textarea:not([disabled]),[role="combobox"]'
58
+ "[data-dialog-initial-focus]:not(:disabled)"
54
59
  )?.focus({ preventScroll: true });
60
+ if (!element.querySelector("[data-dialog-initial-focus]:not(:disabled)"))
61
+ element.querySelector(
62
+ 'input:not([disabled]):not([readonly]):not([type="hidden"]):not([type="file"]):not([type="checkbox"]):not([type="radio"]),textarea:not([disabled]),[role="combobox"]'
63
+ )?.focus({ preventScroll: true });
55
64
  }
56
65
  void surfaceMotion(element, "enter", "0 4px");
57
66
  return;
@@ -81,23 +90,27 @@ function Dialog({
81
90
  {
82
91
  ref: setRef,
83
92
  "aria-labelledby": titleId,
84
- className: `${s.dialog} ${wide ? s.wide : ""} ${closing ? s.closing : ""}`,
93
+ "aria-describedby": descriptionId,
94
+ "aria-busy": busy || void 0,
95
+ role,
96
+ "data-variant": variant,
97
+ className: `${s.dialog} ${wide || size === "large" ? s.wide : ""} ${size === "small" ? s.small : ""} ${closing ? s.closing : ""}`,
85
98
  onCancel: (e) => {
86
99
  e.preventDefault();
87
100
  e.stopPropagation();
88
- onClose();
101
+ if (!busy) onClose();
89
102
  },
90
103
  onMouseDown: (e) => {
91
104
  backdropDown.current = outside(e);
92
105
  },
93
106
  onClick: (e) => {
94
- if (backdropDown.current && outside(e)) onClose();
107
+ if (!busy && backdropDown.current && outside(e)) onClose();
95
108
  backdropDown.current = false;
96
109
  },
97
110
  children: [
98
111
  /* @__PURE__ */ jsxs("header", { className: s.dialogHeader, children: [
99
112
  /* @__PURE__ */ jsx("h2", { id: titleId, children: title }),
100
- /* @__PURE__ */ jsx(IconButton, { label: "\u5173\u95ED\u5F39\u7A97", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
113
+ /* @__PURE__ */ jsx(IconButton, { label: "\u5173\u95ED\u5F39\u7A97", onClick: onClose, disabled: busy, children: /* @__PURE__ */ jsx(X, {}) })
101
114
  ] }),
102
115
  /* @__PURE__ */ jsx("div", { className: s.dialogBody, children }),
103
116
  footer && /* @__PURE__ */ jsx("footer", { className: s.dialogFooter, children: footer })
@@ -43,6 +43,23 @@
43
43
  .wide {
44
44
  width: min(var(--dialog-wide-width), calc(100vw - 32px));
45
45
  }
46
+ .small {
47
+ width: min(420px, calc(100vw - 32px));
48
+ }
49
+ .dialog[data-variant="message"] .dialogHeader {
50
+ border: 0;
51
+ padding-bottom: 12px;
52
+ }
53
+ .dialog[data-variant="message"] .dialogBody {
54
+ padding-top: 0;
55
+ padding-bottom: 24px;
56
+ }
57
+ .dialog[data-variant="message"] .dialogFooter {
58
+ border: 0;
59
+ background: transparent;
60
+ padding-top: 0;
61
+ padding-bottom: 18px;
62
+ }
46
63
  .closing {
47
64
  pointer-events: none;
48
65
  }