@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.
Files changed (204) hide show
  1. package/README.md +540 -2
  2. package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
  3. package/dist/announce.d.ts +10 -0
  4. package/dist/announce.js +30 -0
  5. package/dist/chart-analytic.cjs +33 -25
  6. package/dist/chart-preset.cjs +23 -15
  7. package/dist/chart.cjs +23 -15
  8. package/dist/combobox-model.d.ts +10 -1
  9. package/dist/combobox-model.js +15 -0
  10. package/dist/command-palette-model.d.ts +44 -0
  11. package/dist/command-palette-model.js +36 -0
  12. package/dist/components/AccountMenu.d.ts +44 -0
  13. package/dist/components/AccountMenu.js +43 -0
  14. package/dist/components/AppShell.d.ts +45 -2
  15. package/dist/components/AppShell.js +116 -5
  16. package/dist/components/AuditLog.d.ts +128 -0
  17. package/dist/components/AuditLog.js +196 -0
  18. package/dist/components/BrandProfiles.js +4 -3
  19. package/dist/components/Breadcrumbs.d.ts +9 -2
  20. package/dist/components/Breadcrumbs.js +3 -2
  21. package/dist/components/Button.d.ts +12 -5
  22. package/dist/components/Button.js +7 -3
  23. package/dist/components/Card.d.ts +18 -1
  24. package/dist/components/Card.js +5 -1
  25. package/dist/components/ChangeHighlight.d.ts +30 -0
  26. package/dist/components/ChangeHighlight.js +21 -0
  27. package/dist/components/ChartCore.js +2 -1
  28. package/dist/components/Checkbox.d.ts +27 -1
  29. package/dist/components/Checkbox.js +31 -2
  30. package/dist/components/Chip.d.ts +7 -3
  31. package/dist/components/Chip.js +2 -2
  32. package/dist/components/CodeDisplay.d.ts +65 -0
  33. package/dist/components/CodeDisplay.js +124 -0
  34. package/dist/components/Combobox.d.ts +27 -6
  35. package/dist/components/Combobox.js +195 -58
  36. package/dist/components/CommandPalette.d.ts +36 -1
  37. package/dist/components/CommandPalette.js +254 -2
  38. package/dist/components/ContactCard.d.ts +66 -0
  39. package/dist/components/ContactCard.js +74 -0
  40. package/dist/components/Count.d.ts +24 -0
  41. package/dist/components/Count.js +32 -0
  42. package/dist/components/DashboardCanvas.js +14 -0
  43. package/dist/components/DataWorkspace.d.ts +167 -0
  44. package/dist/components/DataWorkspace.js +380 -0
  45. package/dist/components/DateFieldParts.d.ts +2 -1
  46. package/dist/components/DateFieldParts.js +7 -0
  47. package/dist/components/DatePicker.d.ts +9 -2
  48. package/dist/components/DatePicker.js +67 -28
  49. package/dist/components/DateRangePicker.d.ts +12 -2
  50. package/dist/components/DateRangePicker.js +13 -12
  51. package/dist/components/DetailPage.d.ts +7 -1
  52. package/dist/components/DetailPage.js +2 -1
  53. package/dist/components/Dialogs.d.ts +71 -10
  54. package/dist/components/Dialogs.js +130 -30
  55. package/dist/components/DiffViewer.d.ts +23 -3
  56. package/dist/components/DiffViewer.js +26 -12
  57. package/dist/components/DiscardPrompt.js +16 -0
  58. package/dist/components/DisplayPreferences.d.ts +83 -0
  59. package/dist/components/DisplayPreferences.js +185 -0
  60. package/dist/components/EditorialHome.d.ts +23 -3
  61. package/dist/components/EditorialHome.js +16 -11
  62. package/dist/components/EntityPicker.d.ts +20 -3
  63. package/dist/components/EntityPicker.js +32 -13
  64. package/dist/components/FilterEditor.d.ts +18 -4
  65. package/dist/components/FilterEditor.js +141 -19
  66. package/dist/components/FocusOnMount.d.ts +282 -0
  67. package/dist/components/FocusOnMount.js +12 -0
  68. package/dist/components/InfoTip.js +11 -12
  69. package/dist/components/InlineEdit.d.ts +87 -20
  70. package/dist/components/InlineEdit.js +197 -54
  71. package/dist/components/Input.d.ts +12 -2
  72. package/dist/components/Input.js +5 -3
  73. package/dist/components/Kanban.d.ts +14 -3
  74. package/dist/components/Kanban.js +9 -4
  75. package/dist/components/Kbd.d.ts +6 -1
  76. package/dist/components/Kbd.js +6 -6
  77. package/dist/components/Lightbox.d.ts +41 -5
  78. package/dist/components/Lightbox.js +95 -9
  79. package/dist/components/MatchReview.d.ts +40 -1
  80. package/dist/components/MatchReview.js +130 -4
  81. package/dist/components/MetricInput.js +3 -2
  82. package/dist/components/Modal.d.ts +2 -1
  83. package/dist/components/Modal.js +13 -4
  84. package/dist/components/MotionList.d.ts +303 -0
  85. package/dist/components/MotionList.js +175 -0
  86. package/dist/components/NavFavourites.d.ts +5 -0
  87. package/dist/components/NavFavourites.js +7 -7
  88. package/dist/components/OtpInput.d.ts +33 -0
  89. package/dist/components/OtpInput.js +123 -0
  90. package/dist/components/PageHeader.d.ts +7 -1
  91. package/dist/components/PageHeader.js +2 -2
  92. package/dist/components/Pagination.js +6 -5
  93. package/dist/components/PasswordInput.d.ts +34 -0
  94. package/dist/components/PasswordInput.js +101 -0
  95. package/dist/components/Peek.js +3 -3
  96. package/dist/components/Popover.js +11 -0
  97. package/dist/components/PrintLayout.d.ts +63 -0
  98. package/dist/components/PrintLayout.js +114 -0
  99. package/dist/components/QrCode.d.ts +47 -0
  100. package/dist/components/QrCode.js +55 -0
  101. package/dist/components/QrScanner.d.ts +95 -0
  102. package/dist/components/QrScanner.js +266 -0
  103. package/dist/components/RelationshipGraph.d.ts +32 -1
  104. package/dist/components/RelationshipGraph.js +22 -6
  105. package/dist/components/RelationshipGraphLayered.d.ts +1 -1
  106. package/dist/components/RelationshipGraphLayered.js +268 -118
  107. package/dist/components/RelationshipGraphList.js +33 -6
  108. package/dist/components/RuleBuilder.js +39 -7
  109. package/dist/components/SchedulingAgenda.d.ts +3 -1
  110. package/dist/components/SchedulingCalendar.d.ts +3 -1
  111. package/dist/components/SchedulingCalendar.js +43 -4
  112. package/dist/components/SchedulingResourceDay.d.ts +57 -0
  113. package/dist/components/SchedulingResourceDay.js +287 -0
  114. package/dist/components/SchedulingTimeGrid.d.ts +3 -1
  115. package/dist/components/SchedulingTimeline.d.ts +3 -1
  116. package/dist/components/SearchSuggest.d.ts +23 -3
  117. package/dist/components/SearchSuggest.js +56 -18
  118. package/dist/components/Segmented.d.ts +39 -4
  119. package/dist/components/Segmented.js +39 -5
  120. package/dist/components/Select.d.ts +3 -2
  121. package/dist/components/Select.js +7 -1
  122. package/dist/components/ShortcutHint.d.ts +11 -10
  123. package/dist/components/ShortcutHint.js +44 -38
  124. package/dist/components/Sidebar.d.ts +36 -4
  125. package/dist/components/Sidebar.js +37 -12
  126. package/dist/components/SortableList.d.ts +45 -0
  127. package/dist/components/SortableList.js +147 -0
  128. package/dist/components/Stat.d.ts +28 -3
  129. package/dist/components/Stat.js +13 -3
  130. package/dist/components/States.d.ts +33 -1
  131. package/dist/components/States.js +43 -1
  132. package/dist/components/StatusPill.d.ts +14 -3
  133. package/dist/components/StatusPill.js +11 -3
  134. package/dist/components/Steps.js +2 -1
  135. package/dist/components/Table.d.ts +9 -2
  136. package/dist/components/Table.js +4 -2
  137. package/dist/components/TableControls.js +18 -58
  138. package/dist/components/TableOverflowProbe.d.ts +12 -0
  139. package/dist/components/TableOverflowProbe.js +27 -0
  140. package/dist/components/Tabs.d.ts +2 -1
  141. package/dist/components/Tabs.js +10 -2
  142. package/dist/components/TagInput.d.ts +67 -0
  143. package/dist/components/TagInput.js +342 -0
  144. package/dist/components/Textarea.d.ts +5 -0
  145. package/dist/components/Textarea.js +2 -2
  146. package/dist/components/TimePicker.d.ts +80 -0
  147. package/dist/components/TimePicker.js +347 -0
  148. package/dist/components/Toast.d.ts +1 -1
  149. package/dist/components/Toast.js +109 -4
  150. package/dist/components/Tooltip.d.ts +8 -4
  151. package/dist/components/Tooltip.js +86 -28
  152. package/dist/components/Tree.d.ts +8 -0
  153. package/dist/components/Tree.js +11 -5
  154. package/dist/display-preferences-model.d.ts +53 -0
  155. package/dist/display-preferences-model.js +64 -0
  156. package/dist/emoji.cjs +143 -49
  157. package/dist/filter-model.d.ts +59 -3
  158. package/dist/filter-model.js +67 -2
  159. package/dist/hooks/useAnchoredPosition.js +14 -2
  160. package/dist/hooks/useChangeHighlight.d.ts +33 -0
  161. package/dist/hooks/useChangeHighlight.js +44 -0
  162. package/dist/hooks/useDeferredReorder.d.ts +32 -0
  163. package/dist/hooks/useDeferredReorder.js +47 -0
  164. package/dist/hooks/useDialog.d.ts +5 -0
  165. package/dist/hooks/useDialog.js +17 -3
  166. package/dist/hooks/useKeyboardGrab.d.ts +58 -0
  167. package/dist/hooks/useKeyboardGrab.js +118 -0
  168. package/dist/hooks/useOptimisticValue.d.ts +51 -0
  169. package/dist/hooks/useOptimisticValue.js +61 -0
  170. package/dist/hooks/useReducedMotion.d.ts +4 -1
  171. package/dist/hooks/useReducedMotion.js +7 -1
  172. package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
  173. package/dist/hooks/useResolvedFilterLabels.js +34 -0
  174. package/dist/hooks/useSlidingIndicator.d.ts +18 -0
  175. package/dist/hooks/useSlidingIndicator.js +80 -0
  176. package/dist/hooks/useVirtualRows.d.ts +21 -2
  177. package/dist/hooks/useVirtualRows.js +30 -3
  178. package/dist/icons/Icon.d.ts +1 -1
  179. package/dist/index.cjs +19687 -13423
  180. package/dist/index.d.cts +3419 -651
  181. package/dist/index.d.ts +48 -18
  182. package/dist/index.js +153 -9
  183. package/dist/labels-context.d.ts +93 -3
  184. package/dist/motion.d.ts +11 -0
  185. package/dist/motion.js +35 -0
  186. package/dist/overlay-stack.d.ts +6 -0
  187. package/dist/overlay-stack.js +13 -0
  188. package/dist/qr-model.d.ts +65 -0
  189. package/dist/qr-model.js +323 -0
  190. package/dist/relationship-graph-model.d.ts +92 -3
  191. package/dist/relationship-graph-model.js +134 -15
  192. package/dist/reorder.d.ts +30 -0
  193. package/dist/reorder.js +74 -0
  194. package/dist/rich-text.cjs +46 -4
  195. package/dist/scheduling-resource-model.d.ts +76 -0
  196. package/dist/scheduling-resource-model.js +74 -0
  197. package/dist/search-suggest-model.js +3 -1
  198. package/dist/shell-context.d.ts +11 -0
  199. package/dist/shell-context.js +7 -0
  200. package/dist/time-field-model.d.ts +46 -0
  201. package/dist/time-field-model.js +152 -0
  202. package/dist/toast-store.d.ts +22 -3
  203. package/dist/toast-store.js +26 -9
  204. 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 showEmpty = hasText && !loading && !error && options.length === 0 && empty != null;
76
- const showLoading = loading && options.length === 0;
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 || error != null);
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) onSelect(option.id, option);
88
- else if (footer && index === options.length) footer.onSelect();
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 current = stored < itemCount ? stored : -1;
111
- if (current < 0) return down ? 0 : itemCount - 1;
112
- return (current + (down ? 1 : -1) + itemCount) % itemCount;
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
- loading && /* @__PURE__ */ jsx(Spinner, { className: "uix-search-suggest__spinner", label: loadingLabel }),
226
- !loading && value && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-search-suggest__clear", "aria-label": clearLabel, onMouseDown: keepFocus, onClick: clear, children: /* @__PURE__ */ jsx(ClearIcon, {}) }),
227
- !loading && !value && !focused && shortcutHint && /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", "aria-hidden": "true", children: shortcutHint })
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
- error != null && options.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "uix-search-suggest__state", children: error }),
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
- return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { ref, role: selection === "radio" ? "radiogroup" : "group", className: cx("uix-segmented", className), onKeyDown: handleKeyDown, ...props, children }) });
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
- size?: 'sm' | 'md';
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
- * `<select>` carries `name`, `required`, `form`, `multiple` and the value, so `<option>` children,
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 === "sm" && "uix-select--sm", className),
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
- /** Set the button's `aria-keyshortcuts`. `false` when the button already has its own (a prop, or a Tooltip's). */
9
- ownsAria: boolean;
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). In the button it
13
- * leaves only an empty, hidden marker, so the button's text, name, layout and selectors are
14
- * unchanged (no wrapper, no sibling). The bubble itself is portalled after mount into the
15
- * nearest `<dialog>` (outside one a modal dialog makes everything inert) or `<body>`, as a
16
- * top-layer popover, `aria-hidden`: assistive technology gets the key from the button's
17
- * `aria-keyshortcuts`, which this sets for the viewer's platform (Mod is Meta on Apple, Control
18
- * elsewhere) unless the button already has its own.
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, ownsAria }: ShortcutHintProps): react.JSX.Element;
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, ariaKeyShortcuts, useKbdPlatform } from "./Kbd.js";
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, ownsAria }) {
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 shortcuts = ariaKeyShortcuts(keys, platform);
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 === "Escape") hide();
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
- ref: bubbleRef,
127
- className: "uix-tooltip__bubble uix-tooltip__bubble--hint",
128
- "aria-hidden": "true",
129
- ...bubbleAttr,
130
- onClick: stop,
131
- onDoubleClick: stop,
132
- onContextMenu: stop,
133
- onPointerDown: stop,
134
- onPointerUp: stop,
135
- onMouseDown: stop,
136
- onMouseUp: stop,
137
- children: [
138
- label && /* @__PURE__ */ jsx("span", { children: label }),
139
- /* @__PURE__ */ jsx("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform }) })
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
- declare function NavItem({ icon, badge, active, as: Tag, description, children, className, ...props }: NavItemProps): react.JSX.Element;
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
- declare function SubNavItem({ active, as: Tag, children, className, ...props }: SubNavItemProps): react.JSX.Element;
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 };