@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.
- package/CHANGELOG.md +28 -0
- package/README.md +58 -2
- package/components/AppShell.d.ts +11 -4
- package/components/AppShell.js +79 -54
- package/components/AppShell.module.css +16 -4
- package/components/AppearanceSettings.js +1 -1
- package/components/Button.d.ts +9 -2
- package/components/Button.js +30 -7
- package/components/Button.module.css +16 -7
- package/components/Calendar.d.ts +2 -1
- package/components/Calendar.js +4 -3
- package/components/Card.d.ts +2 -1
- package/components/Card.js +13 -3
- package/components/Card.module.css +19 -0
- package/components/Checkbox.module.css +3 -3
- package/components/Combobox.d.ts +4 -2
- package/components/Combobox.js +162 -76
- package/components/Combobox.module.css +5 -7
- package/components/Control.module.css +18 -5
- package/components/DatePicker.d.ts +3 -0
- package/components/DatePicker.js +41 -21
- package/components/DatePicker.module.css +3 -0
- package/components/Dialog.d.ts +6 -1
- package/components/Dialog.js +19 -6
- package/components/Dialog.module.css +17 -0
- package/components/DialogVariants.d.ts +27 -0
- package/components/DialogVariants.js +161 -0
- package/components/DialogVariants.module.css +29 -0
- package/components/HierarchyPicker.module.css +35 -0
- package/components/IconButton.module.css +5 -2
- package/components/IconPicker.d.ts +14 -4
- package/components/IconPicker.js +67 -25
- package/components/Indicator.module.css +1 -1
- package/components/Input.d.ts +2 -1
- package/components/Input.js +12 -4
- package/components/Input.module.css +18 -1
- package/components/LoadingState.d.ts +3 -1
- package/components/LoadingState.js +26 -15
- package/components/LoadingState.module.css +3 -0
- package/components/MenuItem.d.ts +2 -1
- package/components/MenuItem.js +2 -0
- package/components/MenuItem.module.css +9 -0
- package/components/MultiSelect.d.ts +2 -1
- package/components/MultiSelect.js +66 -2
- package/components/MultiSelect.module.css +38 -0
- package/components/NumberInput.js +1 -0
- package/components/OptionList.module.css +12 -0
- package/components/PasswordField.js +1 -0
- package/components/PinInput.js +1 -0
- package/components/Popover.d.ts +4 -1
- package/components/Popover.js +11 -5
- package/components/Popover.module.css +3 -0
- package/components/RadioGroup.module.css +2 -2
- package/components/SearchField.js +1 -0
- package/components/Select.js +1 -0
- package/components/Select.module.css +3 -0
- package/components/SelectionTag.d.ts +8 -0
- package/components/SelectionTag.js +39 -0
- package/components/SettingsLayout.d.ts +3 -1
- package/components/SettingsLayout.js +19 -3
- package/components/SettingsLayout.module.css +20 -0
- package/components/Switch.module.css +8 -5
- package/components/Table.d.ts +6 -2
- package/components/Table.js +14 -4
- package/components/TagInput.js +11 -23
- package/components/TagInput.module.css +1 -1
- package/components/ThemeProvider.js +17 -3
- package/components/ToastProvider.d.ts +4 -4
- package/components/ToastProvider.js +19 -22
- package/components/TreeGuides.d.ts +9 -0
- package/components/TreeGuides.js +39 -0
- package/components/TreeGuides.module.css +45 -0
- package/components/TreeSelect.d.ts +3 -0
- package/components/TreeSelect.js +49 -21
- package/components/TreeTable.d.ts +15 -0
- package/components/TreeTable.js +151 -0
- package/components/TreeTable.module.css +53 -0
- package/components/VirtualPickerList.d.ts +2 -0
- package/components/VirtualPickerList.js +1 -1
- package/components/VirtualPickerList.module.css +4 -0
- package/components/appearance.d.ts +1 -0
- package/components/appearance.js +15 -6
- package/components/calendarNow.d.ts +5 -0
- package/components/calendarNow.js +27 -0
- package/components/createToaster.d.ts +12 -0
- package/components/createToaster.js +40 -0
- package/components/hierarchy.d.ts +1 -0
- package/components/index.d.ts +6 -3
- package/components/index.js +13 -1
- package/components/usePopover.d.ts +2 -2
- package/package.json +36 -4
- package/styles/base.css +20 -3
- package/styles/blue.css +49 -35
- package/styles/minimal.css +2 -1
package/components/Combobox.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useId,
|
|
6
|
+
useRef,
|
|
7
|
+
useState
|
|
8
|
+
} from "react";
|
|
3
9
|
import { Plus } from "lucide-react";
|
|
4
|
-
import {
|
|
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()
|
|
22
|
-
const
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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:
|
|
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:
|
|
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":
|
|
151
|
-
onKeyDown:
|
|
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
|
-
|
|
276
|
+
Button,
|
|
182
277
|
{
|
|
183
|
-
|
|
184
|
-
|
|
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((
|
|
303
|
+
reserveIcon: filtered.some((option) => !!option.icon),
|
|
200
304
|
selected: value === item.value,
|
|
201
|
-
active:
|
|
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
|
-
|
|
224
|
-
|
|
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(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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";
|
package/components/DatePicker.js
CHANGED
|
@@ -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
|
|
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
|
|
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" ?
|
|
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", " ") ||
|
|
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
|
-
|
|
153
|
+
Button,
|
|
147
154
|
{
|
|
155
|
+
variant: "ghost",
|
|
156
|
+
size: "small",
|
|
148
157
|
type: "button",
|
|
149
|
-
disabled: mode !== "time" && !isDateAllowed(
|
|
158
|
+
disabled: mode !== "time" && !isDateAllowed(today, constraints),
|
|
150
159
|
onClick: () => {
|
|
151
|
-
const
|
|
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__ */
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
}
|
package/components/Dialog.d.ts
CHANGED
|
@@ -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;
|
package/components/Dialog.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|