@tensor_1/react 2.47.0 → 2.49.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/README.md +540 -2
- package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
- package/dist/announce.d.ts +10 -0
- package/dist/announce.js +30 -0
- package/dist/chart-analytic.cjs +33 -25
- package/dist/chart-preset.cjs +23 -15
- package/dist/chart.cjs +23 -15
- package/dist/combobox-model.d.ts +10 -1
- package/dist/combobox-model.js +15 -0
- package/dist/command-palette-model.d.ts +44 -0
- package/dist/command-palette-model.js +36 -0
- package/dist/components/AccountMenu.d.ts +44 -0
- package/dist/components/AccountMenu.js +43 -0
- package/dist/components/AppShell.d.ts +45 -2
- package/dist/components/AppShell.js +116 -5
- package/dist/components/AuditLog.d.ts +128 -0
- package/dist/components/AuditLog.js +196 -0
- package/dist/components/BrandProfiles.js +4 -3
- package/dist/components/Breadcrumbs.d.ts +9 -2
- package/dist/components/Breadcrumbs.js +3 -2
- package/dist/components/Button.d.ts +12 -5
- package/dist/components/Button.js +7 -3
- package/dist/components/Card.d.ts +18 -1
- package/dist/components/Card.js +5 -1
- package/dist/components/ChangeHighlight.d.ts +30 -0
- package/dist/components/ChangeHighlight.js +21 -0
- package/dist/components/ChartCore.js +2 -1
- package/dist/components/Checkbox.d.ts +27 -1
- package/dist/components/Checkbox.js +31 -2
- package/dist/components/Chip.d.ts +7 -3
- package/dist/components/Chip.js +2 -2
- package/dist/components/CodeDisplay.d.ts +65 -0
- package/dist/components/CodeDisplay.js +124 -0
- package/dist/components/Combobox.d.ts +27 -6
- package/dist/components/Combobox.js +195 -58
- package/dist/components/CommandPalette.d.ts +36 -1
- package/dist/components/CommandPalette.js +254 -2
- package/dist/components/ContactCard.d.ts +66 -0
- package/dist/components/ContactCard.js +74 -0
- package/dist/components/Count.d.ts +24 -0
- package/dist/components/Count.js +32 -0
- package/dist/components/DashboardCanvas.js +14 -0
- package/dist/components/DataWorkspace.d.ts +167 -0
- package/dist/components/DataWorkspace.js +380 -0
- package/dist/components/DateFieldParts.d.ts +2 -1
- package/dist/components/DateFieldParts.js +7 -0
- package/dist/components/DatePicker.d.ts +9 -2
- package/dist/components/DatePicker.js +67 -28
- package/dist/components/DateRangePicker.d.ts +12 -2
- package/dist/components/DateRangePicker.js +13 -12
- package/dist/components/DetailPage.d.ts +7 -1
- package/dist/components/DetailPage.js +2 -1
- package/dist/components/Dialogs.d.ts +71 -10
- package/dist/components/Dialogs.js +130 -30
- package/dist/components/DiffViewer.d.ts +23 -3
- package/dist/components/DiffViewer.js +26 -12
- package/dist/components/DiscardPrompt.js +16 -0
- package/dist/components/DisplayPreferences.d.ts +83 -0
- package/dist/components/DisplayPreferences.js +185 -0
- package/dist/components/EditorialHome.d.ts +23 -3
- package/dist/components/EditorialHome.js +16 -11
- package/dist/components/EntityPicker.d.ts +20 -3
- package/dist/components/EntityPicker.js +32 -13
- package/dist/components/FilterEditor.d.ts +18 -4
- package/dist/components/FilterEditor.js +141 -19
- package/dist/components/FocusOnMount.d.ts +282 -0
- package/dist/components/FocusOnMount.js +12 -0
- package/dist/components/InfoTip.js +11 -12
- package/dist/components/InlineEdit.d.ts +87 -20
- package/dist/components/InlineEdit.js +197 -54
- package/dist/components/Input.d.ts +12 -2
- package/dist/components/Input.js +5 -3
- package/dist/components/Kanban.d.ts +14 -3
- package/dist/components/Kanban.js +9 -4
- package/dist/components/Kbd.d.ts +6 -1
- package/dist/components/Kbd.js +6 -6
- package/dist/components/Lightbox.d.ts +41 -5
- package/dist/components/Lightbox.js +95 -9
- package/dist/components/MatchReview.d.ts +40 -1
- package/dist/components/MatchReview.js +130 -4
- package/dist/components/MetricInput.js +3 -2
- package/dist/components/Modal.d.ts +2 -1
- package/dist/components/Modal.js +13 -4
- package/dist/components/MotionList.d.ts +303 -0
- package/dist/components/MotionList.js +175 -0
- package/dist/components/NavFavourites.d.ts +5 -0
- package/dist/components/NavFavourites.js +7 -7
- package/dist/components/OtpInput.d.ts +33 -0
- package/dist/components/OtpInput.js +123 -0
- package/dist/components/PageHeader.d.ts +7 -1
- package/dist/components/PageHeader.js +2 -2
- package/dist/components/Pagination.js +6 -5
- package/dist/components/PasswordInput.d.ts +34 -0
- package/dist/components/PasswordInput.js +101 -0
- package/dist/components/Peek.js +3 -3
- package/dist/components/Popover.js +11 -0
- package/dist/components/PrintLayout.d.ts +63 -0
- package/dist/components/PrintLayout.js +114 -0
- package/dist/components/QrCode.d.ts +47 -0
- package/dist/components/QrCode.js +55 -0
- package/dist/components/QrScanner.d.ts +95 -0
- package/dist/components/QrScanner.js +266 -0
- package/dist/components/RelationshipGraph.d.ts +32 -1
- package/dist/components/RelationshipGraph.js +22 -6
- package/dist/components/RelationshipGraphLayered.d.ts +1 -1
- package/dist/components/RelationshipGraphLayered.js +268 -118
- package/dist/components/RelationshipGraphList.js +33 -6
- package/dist/components/RuleBuilder.js +39 -7
- package/dist/components/SchedulingAgenda.d.ts +3 -1
- package/dist/components/SchedulingCalendar.d.ts +3 -1
- package/dist/components/SchedulingCalendar.js +43 -4
- package/dist/components/SchedulingResourceDay.d.ts +57 -0
- package/dist/components/SchedulingResourceDay.js +287 -0
- package/dist/components/SchedulingTimeGrid.d.ts +3 -1
- package/dist/components/SchedulingTimeline.d.ts +3 -1
- package/dist/components/SearchSuggest.d.ts +23 -3
- package/dist/components/SearchSuggest.js +56 -18
- package/dist/components/Segmented.d.ts +39 -4
- package/dist/components/Segmented.js +39 -5
- package/dist/components/Select.d.ts +3 -2
- package/dist/components/Select.js +7 -1
- package/dist/components/ShortcutHint.d.ts +11 -10
- package/dist/components/ShortcutHint.js +44 -38
- package/dist/components/Sidebar.d.ts +36 -4
- package/dist/components/Sidebar.js +37 -12
- package/dist/components/SortableList.d.ts +45 -0
- package/dist/components/SortableList.js +147 -0
- package/dist/components/Stat.d.ts +28 -3
- package/dist/components/Stat.js +13 -3
- package/dist/components/States.d.ts +33 -1
- package/dist/components/States.js +43 -1
- package/dist/components/StatusPill.d.ts +14 -3
- package/dist/components/StatusPill.js +11 -3
- package/dist/components/Steps.js +2 -1
- package/dist/components/Table.d.ts +9 -2
- package/dist/components/Table.js +4 -2
- package/dist/components/TableControls.js +18 -58
- package/dist/components/TableOverflowProbe.d.ts +12 -0
- package/dist/components/TableOverflowProbe.js +27 -0
- package/dist/components/Tabs.d.ts +2 -1
- package/dist/components/Tabs.js +10 -2
- package/dist/components/TagInput.d.ts +67 -0
- package/dist/components/TagInput.js +342 -0
- package/dist/components/Textarea.d.ts +5 -0
- package/dist/components/Textarea.js +2 -2
- package/dist/components/TimePicker.d.ts +80 -0
- package/dist/components/TimePicker.js +347 -0
- package/dist/components/Toast.d.ts +1 -1
- package/dist/components/Toast.js +109 -4
- package/dist/components/Tooltip.d.ts +8 -4
- package/dist/components/Tooltip.js +86 -28
- package/dist/components/Tree.d.ts +8 -0
- package/dist/components/Tree.js +11 -5
- package/dist/display-preferences-model.d.ts +53 -0
- package/dist/display-preferences-model.js +64 -0
- package/dist/emoji.cjs +143 -49
- package/dist/filter-model.d.ts +59 -3
- package/dist/filter-model.js +67 -2
- package/dist/hooks/useAnchoredPosition.js +14 -2
- package/dist/hooks/useChangeHighlight.d.ts +33 -0
- package/dist/hooks/useChangeHighlight.js +44 -0
- package/dist/hooks/useDeferredReorder.d.ts +32 -0
- package/dist/hooks/useDeferredReorder.js +47 -0
- package/dist/hooks/useDialog.d.ts +5 -0
- package/dist/hooks/useDialog.js +17 -3
- package/dist/hooks/useKeyboardGrab.d.ts +58 -0
- package/dist/hooks/useKeyboardGrab.js +118 -0
- package/dist/hooks/useOptimisticValue.d.ts +51 -0
- package/dist/hooks/useOptimisticValue.js +61 -0
- package/dist/hooks/useReducedMotion.d.ts +4 -1
- package/dist/hooks/useReducedMotion.js +7 -1
- package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
- package/dist/hooks/useResolvedFilterLabels.js +34 -0
- package/dist/hooks/useSlidingIndicator.d.ts +18 -0
- package/dist/hooks/useSlidingIndicator.js +80 -0
- package/dist/hooks/useVirtualRows.d.ts +21 -2
- package/dist/hooks/useVirtualRows.js +30 -3
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/index.cjs +19687 -13423
- package/dist/index.d.cts +3419 -651
- package/dist/index.d.ts +48 -18
- package/dist/index.js +153 -9
- package/dist/labels-context.d.ts +93 -3
- package/dist/motion.d.ts +11 -0
- package/dist/motion.js +35 -0
- package/dist/overlay-stack.d.ts +6 -0
- package/dist/overlay-stack.js +13 -0
- package/dist/qr-model.d.ts +65 -0
- package/dist/qr-model.js +323 -0
- package/dist/relationship-graph-model.d.ts +92 -3
- package/dist/relationship-graph-model.js +134 -15
- package/dist/reorder.d.ts +30 -0
- package/dist/reorder.js +74 -0
- package/dist/rich-text.cjs +46 -4
- package/dist/scheduling-resource-model.d.ts +76 -0
- package/dist/scheduling-resource-model.js +74 -0
- package/dist/search-suggest-model.js +3 -1
- package/dist/shell-context.d.ts +11 -0
- package/dist/shell-context.js +7 -0
- package/dist/time-field-model.d.ts +46 -0
- package/dist/time-field-model.js +152 -0
- package/dist/toast-store.d.ts +22 -3
- package/dist/toast-store.js +26 -9
- package/package.json +2 -1
|
@@ -5,6 +5,7 @@ import { cx } from "../cx.js";
|
|
|
5
5
|
import { useUixLabels } from "../labels-context.js";
|
|
6
6
|
import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
|
|
7
7
|
import { searchSegments } from "../search-suggest-model.js";
|
|
8
|
+
import { useDelayedPending } from "../hooks/useDelayedPending.js";
|
|
8
9
|
import { Spinner } from "./Spinner.js";
|
|
9
10
|
const SearchIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
10
11
|
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
@@ -23,6 +24,7 @@ function SearchSuggest({
|
|
|
23
24
|
onValueChange,
|
|
24
25
|
options,
|
|
25
26
|
onSelect,
|
|
27
|
+
freeText = false,
|
|
26
28
|
label,
|
|
27
29
|
placeholder,
|
|
28
30
|
highlight,
|
|
@@ -47,6 +49,9 @@ function SearchSuggest({
|
|
|
47
49
|
name,
|
|
48
50
|
inputId,
|
|
49
51
|
inputDescribedBy,
|
|
52
|
+
inputLabelledBy,
|
|
53
|
+
disabled = false,
|
|
54
|
+
invalid = false,
|
|
50
55
|
className,
|
|
51
56
|
onBlur,
|
|
52
57
|
...props
|
|
@@ -67,15 +72,34 @@ function SearchSuggest({
|
|
|
67
72
|
};
|
|
68
73
|
const [focused, setFocused] = useState(false);
|
|
69
74
|
const [activeState, setActive] = useState(-1);
|
|
75
|
+
const showSpinner = useDelayedPending(loading) && !disabled;
|
|
76
|
+
const [labelled, setLabelled] = useState(false);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setLabelled((fieldRef.current?.labels?.length ?? 0) > 0);
|
|
79
|
+
});
|
|
70
80
|
const itemCount = options.length + (footer ? 1 : 0);
|
|
71
81
|
const active = activeState < itemCount ? activeState : -1;
|
|
72
82
|
const optionKey = options.map((option) => option.id).join("|");
|
|
73
83
|
useEffect(() => setActive(-1), [optionKey]);
|
|
74
84
|
const hasText = value.trim().length > 0;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
85
|
+
const showLoading = showSpinner && loading && options.length === 0;
|
|
86
|
+
const current = { empty: hasText && error == null && empty != null, error: error ?? null };
|
|
87
|
+
const settled = useRef({ empty: false, error: null });
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!loading) settled.current = current;
|
|
90
|
+
});
|
|
91
|
+
const state = options.length > 0 || showLoading ? null : loading ? settled.current : current;
|
|
92
|
+
const shownError = state?.error ?? null;
|
|
93
|
+
const showEmpty = !!state?.empty && shownError == null;
|
|
77
94
|
const showIdle = idle != null && itemCount === 0 && !loading && error == null && !showEmpty;
|
|
78
|
-
const visible = open && (itemCount > 0 || showLoading || showEmpty || showIdle ||
|
|
95
|
+
const visible = open && !disabled && (itemCount > 0 || showLoading || showEmpty || showIdle || shownError != null);
|
|
96
|
+
const [entered, setEntered] = useState(false);
|
|
97
|
+
const wasVisible = useRef(false);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (!open) setEntered(false);
|
|
100
|
+
else if (!visible && wasVisible.current) setEntered(true);
|
|
101
|
+
wasVisible.current = visible;
|
|
102
|
+
}, [open, visible]);
|
|
79
103
|
const optionId = (index) => `${id}-option-${index}`;
|
|
80
104
|
useEffect(() => {
|
|
81
105
|
if (!visible || active < 0) return;
|
|
@@ -84,8 +108,10 @@ function SearchSuggest({
|
|
|
84
108
|
}, [active, visible]);
|
|
85
109
|
const choose = (index) => {
|
|
86
110
|
const option = options[index];
|
|
87
|
-
if (option)
|
|
88
|
-
|
|
111
|
+
if (option) {
|
|
112
|
+
if (freeText && option.title !== value) onValueChange(option.title);
|
|
113
|
+
onSelect?.(option.id, option);
|
|
114
|
+
} else if (footer && index === options.length) footer.onSelect();
|
|
89
115
|
else return;
|
|
90
116
|
setOpen(false);
|
|
91
117
|
setActive(-1);
|
|
@@ -107,9 +133,9 @@ function SearchSuggest({
|
|
|
107
133
|
return;
|
|
108
134
|
}
|
|
109
135
|
setActive((stored) => {
|
|
110
|
-
const
|
|
111
|
-
if (
|
|
112
|
-
return (
|
|
136
|
+
const current2 = stored < itemCount ? stored : -1;
|
|
137
|
+
if (current2 < 0) return down ? 0 : itemCount - 1;
|
|
138
|
+
return (current2 + (down ? 1 : -1) + itemCount) % itemCount;
|
|
113
139
|
});
|
|
114
140
|
return;
|
|
115
141
|
}
|
|
@@ -121,11 +147,20 @@ function SearchSuggest({
|
|
|
121
147
|
}
|
|
122
148
|
return;
|
|
123
149
|
case "Enter":
|
|
150
|
+
if (loading && !freeText) {
|
|
151
|
+
event.preventDefault();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
124
154
|
if (visible && active >= 0) {
|
|
125
155
|
event.preventDefault();
|
|
126
156
|
choose(active);
|
|
127
157
|
return;
|
|
128
158
|
}
|
|
159
|
+
if (freeText) {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
if (open) setOpen(false);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
129
164
|
if (visible && hasText && options.length) {
|
|
130
165
|
event.preventDefault();
|
|
131
166
|
choose(0);
|
|
@@ -137,7 +172,7 @@ function SearchSuggest({
|
|
|
137
172
|
event.stopPropagation();
|
|
138
173
|
setOpen(false);
|
|
139
174
|
setActive(-1);
|
|
140
|
-
} else if (value) {
|
|
175
|
+
} else if (value && !freeText) {
|
|
141
176
|
event.preventDefault();
|
|
142
177
|
event.stopPropagation();
|
|
143
178
|
onValueChange("");
|
|
@@ -179,13 +214,13 @@ function SearchSuggest({
|
|
|
179
214
|
"div",
|
|
180
215
|
{
|
|
181
216
|
ref: rootRef,
|
|
182
|
-
className: cx("uix-search-suggest", size === "lg" && "uix-search-suggest--lg", className),
|
|
217
|
+
className: cx("uix-search-suggest", size === "lg" && "uix-search-suggest--lg", freeText && "uix-search-suggest--free-text", className),
|
|
183
218
|
"data-open": visible || void 0,
|
|
184
219
|
onBlur: handleBlur,
|
|
185
220
|
...props,
|
|
186
221
|
children: [
|
|
187
222
|
/* @__PURE__ */ jsxs("div", { ref: fieldBoxRef, className: "uix-search-suggest__field", children: [
|
|
188
|
-
/* @__PURE__ */ jsx("span", { className: "uix-search-suggest__lead", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
|
|
223
|
+
!freeText && /* @__PURE__ */ jsx("span", { className: "uix-search-suggest__lead", "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
|
|
189
224
|
/* @__PURE__ */ jsx(
|
|
190
225
|
"input",
|
|
191
226
|
{
|
|
@@ -195,7 +230,10 @@ function SearchSuggest({
|
|
|
195
230
|
className: "uix-input uix-search-suggest__input",
|
|
196
231
|
type: "text",
|
|
197
232
|
role: "combobox",
|
|
198
|
-
"aria-label": label,
|
|
233
|
+
"aria-label": inputLabelledBy || labelled ? void 0 : label,
|
|
234
|
+
"aria-labelledby": inputLabelledBy,
|
|
235
|
+
"aria-invalid": invalid || void 0,
|
|
236
|
+
disabled,
|
|
199
237
|
"aria-autocomplete": "list",
|
|
200
238
|
"aria-expanded": visible,
|
|
201
239
|
"aria-controls": listboxId,
|
|
@@ -203,7 +241,7 @@ function SearchSuggest({
|
|
|
203
241
|
"aria-busy": loading || void 0,
|
|
204
242
|
autoComplete: "off",
|
|
205
243
|
spellCheck: false,
|
|
206
|
-
enterKeyHint: "search",
|
|
244
|
+
enterKeyHint: freeText ? "done" : "search",
|
|
207
245
|
name,
|
|
208
246
|
value,
|
|
209
247
|
placeholder,
|
|
@@ -222,12 +260,12 @@ function SearchSuggest({
|
|
|
222
260
|
}
|
|
223
261
|
),
|
|
224
262
|
/* @__PURE__ */ jsxs("span", { className: "uix-search-suggest__trail", children: [
|
|
225
|
-
|
|
226
|
-
!
|
|
227
|
-
!
|
|
263
|
+
showSpinner && /* @__PURE__ */ jsx(Spinner, { className: "uix-search-suggest__spinner", label: loadingLabel }),
|
|
264
|
+
!showSpinner && value && !disabled && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-search-suggest__clear", "aria-label": clearLabel, onMouseDown: keepFocus, onClick: clear, children: /* @__PURE__ */ jsx(ClearIcon, {}) }),
|
|
265
|
+
!showSpinner && !value && !focused && shortcutHint && /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", "aria-hidden": "true", children: shortcutHint })
|
|
228
266
|
] })
|
|
229
267
|
] }),
|
|
230
|
-
/* @__PURE__ */ jsxs("div", { ref: popupRef, className: "uix-search-suggest__popup", "data-strategy": floating ? "fixed" : void 0, hidden: !visible, children: [
|
|
268
|
+
/* @__PURE__ */ jsxs("div", { ref: popupRef, className: "uix-search-suggest__popup", "data-strategy": floating ? "fixed" : void 0, "data-settled": entered ? "" : void 0, hidden: !visible, children: [
|
|
231
269
|
heading && options.length > 0 && /* @__PURE__ */ jsxs("div", { className: "uix-search-suggest__header", children: [
|
|
232
270
|
/* @__PURE__ */ jsx("span", { className: "uix-search-suggest__heading", id: headingId, children: heading }),
|
|
233
271
|
headingAction && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-search-suggest__header-action", onMouseDown: keepFocus, onClick: headingAction.onSelect, children: headingAction.label })
|
|
@@ -296,7 +334,7 @@ function SearchSuggest({
|
|
|
296
334
|
/* @__PURE__ */ jsx("span", { className: "uix-spinner" }),
|
|
297
335
|
loadingLabel && /* @__PURE__ */ jsx("span", { children: loadingLabel })
|
|
298
336
|
] }),
|
|
299
|
-
|
|
337
|
+
shownError != null && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: shownError }),
|
|
300
338
|
showEmpty && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: empty }),
|
|
301
339
|
showIdle && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: idle }),
|
|
302
340
|
note != null && options.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__note", children: note })
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
import { UixRenderLink } from '../link.js';
|
|
3
4
|
|
|
4
5
|
interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
5
6
|
/** The currently-selected option value. */
|
|
@@ -12,8 +13,15 @@ interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'
|
|
|
12
13
|
* toggle bar. `'radio'`: `role="radiogroup"` of `role="radio"` with `aria-checked`, for a
|
|
13
14
|
* setting with exactly one value (theme, density, language). Name the group with
|
|
14
15
|
* `aria-label` or `aria-labelledby` in either mode. The keyboard model is the same in both.
|
|
16
|
+
*
|
|
17
|
+
* `'link'` (HAR-1380): navigation, not a choice, such as a language switcher where each
|
|
18
|
+
* option is a URL. A `<nav>` (named by your `aria-label`) around a list of links, each
|
|
19
|
+
* option needs an `href`, and the one whose `value` is `value` carries `aria-current="page"`.
|
|
20
|
+
* The same pill look; every link is its own tab stop and Enter follows it, like any nav.
|
|
15
21
|
*/
|
|
16
|
-
selection?: 'toggle' | 'radio';
|
|
22
|
+
selection?: 'toggle' | 'radio' | 'link';
|
|
23
|
+
/** `selection="link"`: renders each option's link with your router. Default: `<a>`. */
|
|
24
|
+
renderLink?: UixRenderLink;
|
|
17
25
|
children?: ReactNode;
|
|
18
26
|
}
|
|
19
27
|
/**
|
|
@@ -27,9 +35,13 @@ interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'
|
|
|
27
35
|
* enabled option and select it, wrapping at the ends; Home and End jump; Left/Right swap in
|
|
28
36
|
* a right-to-left group. Before 2.32.0 every option was its own tab stop.
|
|
29
37
|
*/
|
|
30
|
-
declare function Segmented({ value, onChange, selection, children, className, onKeyDown, ...props }: SegmentedProps): react.JSX.Element;
|
|
38
|
+
declare function Segmented({ value, onChange, selection, renderLink, children, className, onKeyDown, ...props }: SegmentedProps): react.JSX.Element;
|
|
31
39
|
interface SegmentedOptionProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onChange'> {
|
|
32
40
|
value: string;
|
|
41
|
+
/** In a `selection="link"` group: where the option goes. Required there, ignored otherwise. */
|
|
42
|
+
href?: string;
|
|
43
|
+
/** In a `selection="link"` group: the target's language, e.g. `de` on a locale switcher. */
|
|
44
|
+
hrefLang?: string;
|
|
33
45
|
children?: ReactNode;
|
|
34
46
|
}
|
|
35
47
|
/**
|
|
@@ -38,6 +50,29 @@ interface SegmentedOptionProps extends Omit<ButtonHTMLAttributes<HTMLButtonEleme
|
|
|
38
50
|
* `.uix-segmented__option[aria-pressed="true"]` (or `[aria-checked="true"]` in a
|
|
39
51
|
* `selection="radio"` group).
|
|
40
52
|
*/
|
|
41
|
-
declare function SegmentedOption({ value, children, className, onClick, ...props }: SegmentedOptionProps): react.JSX.Element;
|
|
53
|
+
declare function SegmentedOption({ value, href, hrefLang, children, className, onClick, ...props }: SegmentedOptionProps): react.JSX.Element;
|
|
54
|
+
interface LocaleSwitcherLocale {
|
|
55
|
+
/** BCP 47 code: `de`, `bs`, `en-GB`. Sets the link's `lang` and `hreflang`. */
|
|
56
|
+
code: string;
|
|
57
|
+
/** What the option shows: "Deutsch", or a short "DE". */
|
|
58
|
+
label: ReactNode;
|
|
59
|
+
/** This page in that language. */
|
|
60
|
+
href: string;
|
|
61
|
+
}
|
|
62
|
+
interface LocaleSwitcherProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'onChange'> {
|
|
63
|
+
/** One link per language, in the order shown. */
|
|
64
|
+
locales: readonly LocaleSwitcherLocale[];
|
|
65
|
+
/** The code of the page's language; that link is `aria-current="page"`. */
|
|
66
|
+
current: string;
|
|
67
|
+
/** Names the navigation, in the page's language: "Language", "Sprache". */
|
|
68
|
+
label: string;
|
|
69
|
+
/** Renders each link with your router (locale-aware links keep client navigation). */
|
|
70
|
+
renderLink?: UixRenderLink;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A language switcher (HAR-1380; MOTUS C-12): a `Segmented selection="link"` whose links carry
|
|
74
|
+
* `lang` and `hreflang`, so each language's name is read in that language.
|
|
75
|
+
*/
|
|
76
|
+
declare function LocaleSwitcher({ locales, current, label, renderLink, ...props }: LocaleSwitcherProps): react.JSX.Element;
|
|
42
77
|
|
|
43
|
-
export { Segmented, SegmentedOption, type SegmentedOptionProps, type SegmentedProps };
|
|
78
|
+
export { LocaleSwitcher, type LocaleSwitcherLocale, type LocaleSwitcherProps, Segmented, SegmentedOption, type SegmentedOptionProps, type SegmentedProps };
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useContext, useEffect, useMemo, useRef } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
|
+
import { renderUixLink } from "../link.js";
|
|
6
|
+
import { useSlidingIndicator } from "../hooks/useSlidingIndicator.js";
|
|
5
7
|
const SegmentedCtx = createContext({});
|
|
6
8
|
const OPTION = ".uix-segmented__option";
|
|
7
|
-
function Segmented({ value, onChange, selection = "toggle", children, className, onKeyDown, ...props }) {
|
|
9
|
+
function Segmented({ value, onChange, selection = "toggle", renderLink, children, className, onKeyDown, ...props }) {
|
|
8
10
|
const ref = useRef(null);
|
|
9
|
-
const contextValue = useMemo(() => ({ value, onChange, selection }), [value, onChange, selection]);
|
|
11
|
+
const contextValue = useMemo(() => ({ value, onChange, selection, renderLink }), [value, onChange, selection, renderLink]);
|
|
12
|
+
const indicatorRef = useRef(null);
|
|
13
|
+
useSlidingIndicator(ref, indicatorRef, `${OPTION}:is([aria-pressed="true"], [aria-checked="true"])`);
|
|
10
14
|
useEffect(() => {
|
|
11
15
|
const group = ref.current;
|
|
12
|
-
if (!group) return;
|
|
16
|
+
if (!group || selection === "link") return;
|
|
13
17
|
const all = Array.from(group.querySelectorAll(OPTION));
|
|
14
18
|
const enabled = all.filter((o) => !o.disabled);
|
|
15
19
|
const current = enabled.find((o) => o.getAttribute("aria-pressed") === "true" || o.getAttribute("aria-checked") === "true") ?? enabled[0];
|
|
@@ -55,10 +59,18 @@ function Segmented({ value, onChange, selection = "toggle", children, className,
|
|
|
55
59
|
target.focus();
|
|
56
60
|
target.click();
|
|
57
61
|
};
|
|
58
|
-
|
|
62
|
+
if (selection === "link") {
|
|
63
|
+
return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("nav", { className: cx("uix-segmented-nav", className), onKeyDown, ...props, children: /* @__PURE__ */ jsx("ul", { className: "uix-segmented", role: "list", children }) }) });
|
|
64
|
+
}
|
|
65
|
+
return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { ref, role: selection === "radio" ? "radiogroup" : "group", className: cx("uix-segmented", className), onKeyDown: handleKeyDown, ...props, children: [
|
|
66
|
+
children,
|
|
67
|
+
/* @__PURE__ */ jsx("span", { ref: indicatorRef, className: "uix-segmented__indicator", "aria-hidden": "true", hidden: true })
|
|
68
|
+
] }) });
|
|
59
69
|
}
|
|
60
70
|
function SegmentedOption({
|
|
61
71
|
value,
|
|
72
|
+
href,
|
|
73
|
+
hrefLang,
|
|
62
74
|
children,
|
|
63
75
|
className,
|
|
64
76
|
onClick,
|
|
@@ -67,6 +79,24 @@ function SegmentedOption({
|
|
|
67
79
|
const ctx = useContext(SegmentedCtx);
|
|
68
80
|
const selected = ctx.value === value;
|
|
69
81
|
const radio = ctx.selection === "radio";
|
|
82
|
+
if (ctx.selection === "link") {
|
|
83
|
+
const { type: _type, disabled, form: _form, formAction: _formAction, name: _name, ...linkProps } = props;
|
|
84
|
+
if (disabled || !href) {
|
|
85
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", { ...linkProps, hrefLang, className: cx("uix-segmented__option", className), "aria-disabled": "true", "aria-current": selected ? "page" : void 0, children }) });
|
|
86
|
+
}
|
|
87
|
+
return /* @__PURE__ */ jsx("li", { children: renderUixLink(ctx.renderLink, {
|
|
88
|
+
...linkProps,
|
|
89
|
+
href,
|
|
90
|
+
hrefLang,
|
|
91
|
+
className: cx("uix-segmented__option", className),
|
|
92
|
+
"aria-current": selected ? "page" : void 0,
|
|
93
|
+
onClick: (event) => {
|
|
94
|
+
ctx.onChange?.(value);
|
|
95
|
+
onClick?.(event);
|
|
96
|
+
},
|
|
97
|
+
children
|
|
98
|
+
}) });
|
|
99
|
+
}
|
|
70
100
|
return /* @__PURE__ */ jsx(
|
|
71
101
|
"button",
|
|
72
102
|
{
|
|
@@ -85,7 +115,11 @@ function SegmentedOption({
|
|
|
85
115
|
}
|
|
86
116
|
);
|
|
87
117
|
}
|
|
118
|
+
function LocaleSwitcher({ locales, current, label, renderLink, ...props }) {
|
|
119
|
+
return /* @__PURE__ */ jsx(Segmented, { selection: "link", value: current, "aria-label": label, renderLink, ...props, children: locales.map((locale) => /* @__PURE__ */ jsx(SegmentedOption, { value: locale.code, href: locale.href, hrefLang: locale.code, lang: locale.code, children: locale.label }, locale.code)) });
|
|
120
|
+
}
|
|
88
121
|
export {
|
|
122
|
+
LocaleSwitcher,
|
|
89
123
|
Segmented,
|
|
90
124
|
SegmentedOption
|
|
91
125
|
};
|
|
@@ -27,7 +27,8 @@ interface SelectLabels {
|
|
|
27
27
|
results: string;
|
|
28
28
|
}
|
|
29
29
|
interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
|
|
30
|
-
|
|
30
|
+
/** `sm` 28 px, `md` 36 px (default), `lg` 44 px, the public touch-target size (HAR-1812). */
|
|
31
|
+
size?: 'sm' | 'md' | 'lg';
|
|
31
32
|
/** The options as data, instead of `<option>` / `<optgroup>` children. */
|
|
32
33
|
options?: readonly (SelectOption | SelectGroup)[];
|
|
33
34
|
/**
|
|
@@ -64,7 +65,7 @@ interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'siz
|
|
|
64
65
|
* A select that draws its own list (HAR-1572): the WAI-ARIA APG select-only combobox — focus
|
|
65
66
|
* stays on the trigger, `aria-activedescendant` marks the active option — in a top-layer popover
|
|
66
67
|
* placed with `useAnchoredPosition`, or a bottom sheet on a phone. A visually hidden native
|
|
67
|
-
*
|
|
68
|
+
* select element (the form proxy) carries `name`, `required`, `form`, `multiple` and the value, so `<option>` children,
|
|
68
69
|
* `onChange(e)` with `e.target.value`, FormData, `form.reset()`, `ref` and react-hook-form keep
|
|
69
70
|
* working; `ref` points at that select.
|
|
70
71
|
*/
|
|
@@ -4,6 +4,7 @@ import { Children, Fragment as Fragment2, forwardRef, isValidElement, useCallbac
|
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
import { useUixLabels } from "../labels-context.js";
|
|
6
6
|
import { useAnchoredPosition, zoomOf } from "../hooks/useAnchoredPosition.js";
|
|
7
|
+
import { markKeyOpened, watchInputModality } from "../motion.js";
|
|
7
8
|
import { Drawer } from "./Drawer.js";
|
|
8
9
|
import {
|
|
9
10
|
buildSelectModel,
|
|
@@ -542,18 +543,23 @@ const Select = forwardRef(function Select2(props, forwardedRef) {
|
|
|
542
543
|
const room = Math.max(r.top, window.innerHeight - r.bottom) / zoom - OFFSET - EDGE;
|
|
543
544
|
popup.style.minWidth = `${Math.round(r.width / zoom)}px`;
|
|
544
545
|
popup.style.maxHeight = `${Math.max(96, Math.min(MAX_HEIGHT, Math.floor(room)))}px`;
|
|
546
|
+
if (!isOpen()) markKeyOpened(popup);
|
|
545
547
|
try {
|
|
546
548
|
if (!isOpen()) popup.showPopover();
|
|
547
549
|
} catch {
|
|
548
550
|
}
|
|
549
551
|
if (isSearchable) searchRef.current?.focus({ preventScroll: true });
|
|
550
552
|
} else {
|
|
553
|
+
if (isOpen()) markKeyOpened(popup);
|
|
551
554
|
try {
|
|
552
555
|
if (isOpen()) popup.hidePopover();
|
|
553
556
|
} catch {
|
|
554
557
|
}
|
|
555
558
|
}
|
|
556
559
|
}, [showing, isSearchable]);
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
watchInputModality();
|
|
562
|
+
}, []);
|
|
557
563
|
useAnchoredPosition(triggerRef, popupRef, { open: showing, placement, offset: OFFSET, padding: EDGE });
|
|
558
564
|
useEffect(() => {
|
|
559
565
|
if (open && mode === "sheet") (isSearchable ? searchRef.current : listRef.current)?.focus({ preventScroll: true });
|
|
@@ -706,7 +712,7 @@ const Select = forwardRef(function Select2(props, forwardedRef) {
|
|
|
706
712
|
ref: triggerRef,
|
|
707
713
|
type: "button",
|
|
708
714
|
role: "combobox",
|
|
709
|
-
className: cx("uix-select", size
|
|
715
|
+
className: cx("uix-select", size !== "md" && `uix-select--${size}`, className),
|
|
710
716
|
style,
|
|
711
717
|
disabled,
|
|
712
718
|
"aria-label": ariaLabel,
|
|
@@ -5,17 +5,18 @@ interface ShortcutHintProps {
|
|
|
5
5
|
keys: KeyShortcut;
|
|
6
6
|
/** Text before the keys, e.g. an icon button's name. */
|
|
7
7
|
label?: string;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/** The control's own `aria-describedby`: the spoken keys are added after it, never instead. */
|
|
9
|
+
describedBy?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* The hover / keyboard-focus hint of a `Button` with `shortcut` (HAR-2031
|
|
13
|
-
* leaves only an empty, hidden marker, so the
|
|
14
|
-
* unchanged (no wrapper, no sibling). The bubble itself is portalled
|
|
15
|
-
* nearest `<dialog>` (outside one a modal dialog makes everything inert) or
|
|
16
|
-
* top-layer popover, `aria-hidden
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* The hover / keyboard-focus hint of a `Button` or `ButtonLink` with `shortcut` (HAR-2031,
|
|
13
|
+
* HAR-2416). In the control it leaves only an empty, hidden marker, so the control's text, name,
|
|
14
|
+
* layout and selectors are unchanged (no wrapper, no sibling). The bubble itself is portalled
|
|
15
|
+
* after mount into the nearest `<dialog>` (outside one a modal dialog makes everything inert) or
|
|
16
|
+
* `<body>`, as a top-layer popover, `aria-hidden`. Screen readers hear the keys once, as the
|
|
17
|
+
* control's description ("Control+K"; ⌘ is "Command" on Apple), the same way a `Tooltip` with a
|
|
18
|
+
* shortcut says them: the description is the one channel every screen reader announces, and an
|
|
19
|
+
* `aria-keyshortcuts` beside it was read a second time by those that announce that too.
|
|
19
20
|
*
|
|
20
21
|
* It opens on hover and on keyboard focus (`:focus-visible`), so focus a menu or dialog sends back
|
|
21
22
|
* after a mouse choice does not open it. It closes on leave, blur, Escape and when the button is
|
|
@@ -24,6 +25,6 @@ interface ShortcutHintProps {
|
|
|
24
25
|
*
|
|
25
26
|
* It is its own client component so that `Button`, which has no hooks, still renders on a server.
|
|
26
27
|
*/
|
|
27
|
-
declare function ShortcutHint({ keys, label,
|
|
28
|
+
declare function ShortcutHint({ keys, label, describedBy }: ShortcutHintProps): react.JSX.Element;
|
|
28
29
|
|
|
29
30
|
export { ShortcutHint, type ShortcutHintProps };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
|
|
6
|
-
import { KbdCombo,
|
|
6
|
+
import { KbdCombo, spokenKeys, useKbdPlatform } from "./Kbd.js";
|
|
7
7
|
const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
|
|
8
8
|
const isShown = (el) => {
|
|
9
9
|
try {
|
|
@@ -20,26 +20,15 @@ const focusVisible = (el) => {
|
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
const stop = (event) => event.stopPropagation();
|
|
23
|
-
function ShortcutHint({ keys, label,
|
|
23
|
+
function ShortcutHint({ keys, label, describedBy }) {
|
|
24
24
|
const markerRef = useRef(null);
|
|
25
25
|
const bubbleRef = useRef(null);
|
|
26
26
|
const anchorRef = useRef(null);
|
|
27
27
|
const [host, setHost] = useState(null);
|
|
28
28
|
const [open, setOpen] = useState(false);
|
|
29
29
|
const platform = useKbdPlatform();
|
|
30
|
-
const
|
|
30
|
+
const spokenId = `${useId()}-keys`;
|
|
31
31
|
useAnchoredPosition(anchorRef, bubbleRef, { open, placement: "top", offset: 6 });
|
|
32
|
-
useIsomorphicLayoutEffect(() => {
|
|
33
|
-
const button = markerRef.current?.parentElement;
|
|
34
|
-
if (!button || !ownsAria) return void 0;
|
|
35
|
-
const before = button.getAttribute("aria-keyshortcuts");
|
|
36
|
-
button.setAttribute("aria-keyshortcuts", shortcuts);
|
|
37
|
-
return () => {
|
|
38
|
-
if (button.getAttribute("aria-keyshortcuts") !== shortcuts) return;
|
|
39
|
-
if (before == null) button.removeAttribute("aria-keyshortcuts");
|
|
40
|
-
else button.setAttribute("aria-keyshortcuts", before);
|
|
41
|
-
};
|
|
42
|
-
}, [shortcuts, ownsAria]);
|
|
43
32
|
useIsomorphicLayoutEffect(() => {
|
|
44
33
|
const button = markerRef.current?.parentElement ?? null;
|
|
45
34
|
anchorRef.current = button;
|
|
@@ -49,6 +38,17 @@ function ShortcutHint({ keys, label, ownsAria }) {
|
|
|
49
38
|
}
|
|
50
39
|
setHost(button.closest("dialog") ?? button.ownerDocument.body);
|
|
51
40
|
}, []);
|
|
41
|
+
useIsomorphicLayoutEffect(() => {
|
|
42
|
+
const control = anchorRef.current;
|
|
43
|
+
if (!host || !control) return void 0;
|
|
44
|
+
const value = [describedBy, spokenId].filter(Boolean).join(" ");
|
|
45
|
+
control.setAttribute("aria-describedby", value);
|
|
46
|
+
return () => {
|
|
47
|
+
if (control.getAttribute("aria-describedby") !== value) return;
|
|
48
|
+
if (describedBy) control.setAttribute("aria-describedby", describedBy);
|
|
49
|
+
else control.removeAttribute("aria-describedby");
|
|
50
|
+
};
|
|
51
|
+
}, [host, describedBy, spokenId]);
|
|
52
52
|
useEffect(() => {
|
|
53
53
|
const button = anchorRef.current;
|
|
54
54
|
const bubble = bubbleRef.current;
|
|
@@ -85,7 +85,10 @@ function ShortcutHint({ keys, label, ownsAria }) {
|
|
|
85
85
|
if (focusVisible(button)) show();
|
|
86
86
|
};
|
|
87
87
|
const onKey = (event) => {
|
|
88
|
-
if (event.key
|
|
88
|
+
if (event.key !== "Escape" || !isShown(bubble)) return;
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
event.stopPropagation();
|
|
91
|
+
hide();
|
|
89
92
|
};
|
|
90
93
|
const watch = new MutationObserver(() => {
|
|
91
94
|
if (button.hasAttribute("disabled") || button.getAttribute("aria-disabled") === "true") hide();
|
|
@@ -97,7 +100,7 @@ function ShortcutHint({ keys, label, ownsAria }) {
|
|
|
97
100
|
button.addEventListener("blur", hide);
|
|
98
101
|
bubble.addEventListener("mouseenter", show);
|
|
99
102
|
bubble.addEventListener("mouseleave", hideSoon);
|
|
100
|
-
button.ownerDocument.addEventListener("keydown", onKey);
|
|
103
|
+
button.ownerDocument.addEventListener("keydown", onKey, true);
|
|
101
104
|
return () => {
|
|
102
105
|
window.clearTimeout(timer);
|
|
103
106
|
watch.disconnect();
|
|
@@ -107,7 +110,7 @@ function ShortcutHint({ keys, label, ownsAria }) {
|
|
|
107
110
|
button.removeEventListener("blur", hide);
|
|
108
111
|
bubble.removeEventListener("mouseenter", show);
|
|
109
112
|
bubble.removeEventListener("mouseleave", hideSoon);
|
|
110
|
-
button.ownerDocument.removeEventListener("keydown", onKey);
|
|
113
|
+
button.ownerDocument.removeEventListener("keydown", onKey, true);
|
|
111
114
|
if (isShown(bubble)) {
|
|
112
115
|
try {
|
|
113
116
|
bubble.hidePopover();
|
|
@@ -120,26 +123,29 @@ function ShortcutHint({ keys, label, ownsAria }) {
|
|
|
120
123
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
121
124
|
/* @__PURE__ */ jsx("span", { ref: markerRef, hidden: true, "data-uix-shortcut-hint": "" }),
|
|
122
125
|
host && createPortal(
|
|
123
|
-
/* @__PURE__ */ jsxs(
|
|
124
|
-
"span",
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
127
|
+
/* @__PURE__ */ jsx("span", { id: spokenId, hidden: true, children: spokenKeys(keys, platform) }),
|
|
128
|
+
/* @__PURE__ */ jsxs(
|
|
129
|
+
"span",
|
|
130
|
+
{
|
|
131
|
+
ref: bubbleRef,
|
|
132
|
+
className: "uix-tooltip__bubble uix-tooltip__bubble--hint",
|
|
133
|
+
"aria-hidden": "true",
|
|
134
|
+
...bubbleAttr,
|
|
135
|
+
onClick: stop,
|
|
136
|
+
onDoubleClick: stop,
|
|
137
|
+
onContextMenu: stop,
|
|
138
|
+
onPointerDown: stop,
|
|
139
|
+
onPointerUp: stop,
|
|
140
|
+
onMouseDown: stop,
|
|
141
|
+
onMouseUp: stop,
|
|
142
|
+
children: [
|
|
143
|
+
label && /* @__PURE__ */ jsx("span", { children: label }),
|
|
144
|
+
/* @__PURE__ */ jsx("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform }) })
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
] }),
|
|
143
149
|
host
|
|
144
150
|
)
|
|
145
151
|
] });
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, AnchorHTMLAttributes, HTMLAttributes } from 'react';
|
|
3
|
+
import { KeyShortcut } from './Kbd.js';
|
|
3
4
|
|
|
5
|
+
/** The search row under the sidebar head (HAR-1371): it opens your search or command palette. */
|
|
6
|
+
interface SidebarSearch {
|
|
7
|
+
/** The row's words, e.g. "Search". In the rail they are the button's name only. */
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* The keys that open the same search, shown at the end of the row and given to assistive
|
|
11
|
+
* technology as `aria-keyshortcuts`, e.g. `['Mod', 'K']`. Showing them does not bind them:
|
|
12
|
+
* your command palette (or app) listens for the keys.
|
|
13
|
+
*/
|
|
14
|
+
shortcut?: KeyShortcut;
|
|
15
|
+
onActivate: () => void;
|
|
16
|
+
}
|
|
4
17
|
interface SidebarProps extends HTMLAttributes<HTMLElement> {
|
|
5
18
|
collapsed?: boolean;
|
|
6
19
|
nav?: 'full' | 'rail';
|
|
7
20
|
onToggle?: () => void;
|
|
8
21
|
brand?: ReactNode;
|
|
9
22
|
toggleIcon?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Controls in the head beside the brand (HAR-1371): a mode toggle, a "…" utility `Menu`,
|
|
25
|
+
* collapse-all. Icon buttons (`Button icon size="sm"`) fit the row. Hidden in the rail.
|
|
26
|
+
*/
|
|
27
|
+
actions?: ReactNode;
|
|
28
|
+
/** A search row under the head that opens your search (HAR-1371). */
|
|
29
|
+
search?: SidebarSearch;
|
|
30
|
+
/**
|
|
31
|
+
* A slot pinned to the bottom of the sidebar (HAR-1371), for an `AccountMenu`. In the rail it
|
|
32
|
+
* keeps what it renders; give an `AccountMenu` `collapsed` there.
|
|
33
|
+
*/
|
|
34
|
+
footer?: ReactNode;
|
|
10
35
|
children?: ReactNode;
|
|
11
36
|
expandLabel?: string;
|
|
12
37
|
collapseLabel?: string;
|
|
13
38
|
}
|
|
14
|
-
declare function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, children, expandLabel: expandLabelProp, collapseLabel: collapseLabelProp, className, ...props }: SidebarProps): react.JSX.Element;
|
|
39
|
+
declare function Sidebar({ collapsed, nav, onToggle, brand, toggleIcon, actions, search, footer, children, expandLabel: expandLabelProp, collapseLabel: collapseLabelProp, className, ...props }: SidebarProps): react.JSX.Element;
|
|
15
40
|
interface SidebarSectionProps {
|
|
16
41
|
label?: string;
|
|
17
42
|
children?: ReactNode;
|
|
@@ -29,7 +54,13 @@ interface NavItemProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
|
29
54
|
description?: ReactNode;
|
|
30
55
|
children?: ReactNode;
|
|
31
56
|
}
|
|
32
|
-
|
|
57
|
+
/**
|
|
58
|
+
* One navigation row: a link (`as="a"`, default) or, for a row that acts in place (a mode toggle,
|
|
59
|
+
* an account menu), a `button`. The forwarded ref reaches the rendered `<a>` or `<button>`
|
|
60
|
+
* (HAR-1810), so a row can anchor a `Popover` (`anchor={ref}`); `popoverTarget` and other button
|
|
61
|
+
* attributes pass through with `as="button"`.
|
|
62
|
+
*/
|
|
63
|
+
declare const NavItem: react.ForwardRefExoticComponent<NavItemProps & react.RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
|
|
33
64
|
interface NavGroupProps {
|
|
34
65
|
icon?: ReactNode;
|
|
35
66
|
label: ReactNode;
|
|
@@ -44,6 +75,7 @@ interface SubNavItemProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
|
44
75
|
as?: 'a' | 'button';
|
|
45
76
|
children?: ReactNode;
|
|
46
77
|
}
|
|
47
|
-
|
|
78
|
+
/** A nested navigation row. Like `NavItem`, it forwards its ref to the `<a>` or `<button>` (HAR-1810). */
|
|
79
|
+
declare const SubNavItem: react.ForwardRefExoticComponent<SubNavItemProps & react.RefAttributes<HTMLButtonElement | HTMLAnchorElement>>;
|
|
48
80
|
|
|
49
|
-
export { NavGroup, type NavGroupProps, NavItem, type NavItemProps, Sidebar, type SidebarProps, SidebarSection, type SidebarSectionProps, SubNavItem, type SubNavItemProps };
|
|
81
|
+
export { NavGroup, type NavGroupProps, NavItem, type NavItemProps, Sidebar, type SidebarProps, type SidebarSearch, SidebarSection, type SidebarSectionProps, SubNavItem, type SubNavItemProps };
|