@dbcdk/react-components 0.0.211 → 0.0.212

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 (53) hide show
  1. package/README.md +6 -1
  2. package/dist/components/button/Button.cjs +2 -1
  3. package/dist/components/button/Button.d.ts +6 -1
  4. package/dist/components/button/Button.js +2 -1
  5. package/dist/components/button/Button.module.css +14 -0
  6. package/dist/components/color-scheme-button/ColorSchemeButton.cjs +2 -2
  7. package/dist/components/color-scheme-button/ColorSchemeButton.js +2 -2
  8. package/dist/components/forms/file-upload/FileUpload.cjs +4 -0
  9. package/dist/components/forms/file-upload/FileUpload.js +4 -0
  10. package/dist/components/forms/form-select/FormSelect.cjs +4 -0
  11. package/dist/components/forms/form-select/FormSelect.d.ts +1 -1
  12. package/dist/components/forms/form-select/FormSelect.js +4 -0
  13. package/dist/components/forms/input/Input.cjs +4 -0
  14. package/dist/components/forms/input/Input.js +4 -0
  15. package/dist/components/forms/radio-buttons/RadioButtonGroup.cjs +6 -5
  16. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +2 -5
  17. package/dist/components/forms/radio-buttons/RadioButtons.module.css +15 -0
  18. package/dist/components/forms/select/Select.cjs +4 -0
  19. package/dist/components/forms/select/Select.d.ts +1 -1
  20. package/dist/components/forms/select/Select.js +4 -0
  21. package/dist/components/forms/text-area/Textarea.cjs +4 -0
  22. package/dist/components/forms/text-area/Textarea.js +4 -0
  23. package/dist/components/forms/typeahead/Typeahead.cjs +109 -42
  24. package/dist/components/forms/typeahead/Typeahead.d.ts +6 -0
  25. package/dist/components/forms/typeahead/Typeahead.js +109 -42
  26. package/dist/components/forms/typeahead/Typeahead.module.css +16 -0
  27. package/dist/components/headline/CollapsibleHeadline.cjs +15 -18
  28. package/dist/components/headline/CollapsibleHeadline.d.ts +2 -2
  29. package/dist/components/headline/CollapsibleHeadline.js +15 -18
  30. package/dist/components/headline/Headline.module.css +22 -0
  31. package/dist/components/sidebar/components/SidebarItem.cjs +2 -0
  32. package/dist/components/sidebar/components/SidebarItem.d.ts +2 -1
  33. package/dist/components/sidebar/components/SidebarItem.js +2 -0
  34. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.cjs +2 -0
  35. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +2 -1
  36. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +2 -0
  37. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +1 -1
  38. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +40 -0
  39. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.cjs +2 -0
  40. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +2 -1
  41. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +2 -0
  42. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +9 -0
  43. package/dist/components/sidebar/components/sidebar-items/SidebarItems.cjs +6 -4
  44. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +6 -4
  45. package/dist/components/theme-script/ThemeScript.cjs +1 -1
  46. package/dist/components/theme-script/ThemeScript.d.ts +1 -1
  47. package/dist/components/theme-script/ThemeScript.js +1 -1
  48. package/dist/hooks/useColorScheme.cjs +1 -1
  49. package/dist/hooks/useColorScheme.js +1 -1
  50. package/dist/hooks/useGetActiveTheme.cjs +1 -1
  51. package/dist/hooks/useGetActiveTheme.js +1 -1
  52. package/dist/styles/themes/dbc/colors.css +4 -4
  53. package/package.json +1 -1
package/README.md CHANGED
@@ -100,6 +100,11 @@ export default function Header() {
100
100
  }
101
101
  ```
102
102
 
103
+ Light is the default color scheme, including before hydration. `ThemeScript`,
104
+ `useColorScheme()`, and the color scheme controls preserve saved Dark or System
105
+ preferences. To default to the OS preference explicitly, use
106
+ `<ThemeScript defaultColorScheme="system" />` and `useColorScheme('system')`.
107
+
103
108
  The hook updates `data-product-theme`, applies the selected product theme's
104
109
  token overrides, and persists the selection.
105
110
 
@@ -150,7 +155,7 @@ elsewhere it's the `storageKey` option passed to `useColorScheme`):
150
155
 
151
156
  ```tsx
152
157
  const { theme, switchTheme } = useTheme({ defaultTheme: 'dbc', storageKey: 'my-app-theme' })
153
- const { colorScheme, switchColorScheme } = useColorScheme('system', {
158
+ const { colorScheme, switchColorScheme } = useColorScheme(undefined, {
154
159
  storageKey: 'my-app-color-scheme',
155
160
  })
156
161
  ```
@@ -54,7 +54,8 @@ const Button = React__namespace.forwardRef(function Button2({
54
54
  const computedClassName = cx(
55
55
  styles__default.default.button,
56
56
  styles__default.default[variant],
57
- styles__default.default[size],
57
+ // A class name can't start with a digit.
58
+ size === "2xs" ? styles__default.default.size2xs : styles__default.default[size],
58
59
  fullWidth ? styles__default.default.fullWidth : "",
59
60
  active ? styles__default.default.active : "",
60
61
  loading ? styles__default.default.loading : "",
@@ -7,7 +7,12 @@ export type ButtonSize = Exclude<Size, 'xl'>;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
8
8
  variant?: ButtonVariant;
9
9
  shape?: 'default' | 'rounded' | 'round';
10
- size?: ButtonSize;
10
+ /**
11
+ * `2xs`: an icon button that fits within a line of text, e.g. next to a form label - no
12
+ * border or padding, so it doesn't make the line taller. Use it with `variant="inline"`
13
+ * and an `aria-label`. Its click target is small, so keep other controls at a distance.
14
+ */
15
+ size?: ButtonSize | '2xs';
11
16
  fullWidth?: boolean;
12
17
  icon?: React.ReactNode;
13
18
  loading?: boolean;
@@ -30,7 +30,8 @@ const Button = React.forwardRef(function Button2({
30
30
  const computedClassName = cx(
31
31
  styles.button,
32
32
  styles[variant],
33
- styles[size],
33
+ // A class name can't start with a digit.
34
+ size === "2xs" ? styles.size2xs : styles[size],
34
35
  fullWidth ? styles.fullWidth : "",
35
36
  active ? styles.active : "",
36
37
  loading ? styles.loading : "",
@@ -292,3 +292,17 @@
292
292
  inline-size: var(--icon-size-sm);
293
293
  block-size: var(--icon-size-sm);
294
294
  }
295
+
296
+ /* Fits within a line of text: no border or padding to make the line taller. */
297
+ .button.size2xs {
298
+ height: auto;
299
+ min-block-size: 0;
300
+ padding: 0;
301
+ border-width: 0;
302
+ line-height: 1;
303
+ }
304
+
305
+ .button.size2xs svg {
306
+ inline-size: var(--icon-size-xs);
307
+ block-size: var(--icon-size-xs);
308
+ }
@@ -16,7 +16,7 @@ const COLOR_SCHEME_OPTIONS = [
16
16
  function ColorSchemeMenuSection({
17
17
  storageKey
18
18
  } = {}) {
19
- const { colorScheme, switchColorScheme } = useColorScheme.useColorScheme("system", { storageKey });
19
+ const { colorScheme, switchColorScheme } = useColorScheme.useColorScheme(void 0, { storageKey });
20
20
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21
21
  /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: "Udseende" }),
22
22
  COLOR_SCHEME_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -37,7 +37,7 @@ function ColorSchemeButton({
37
37
  variant = "outlined",
38
38
  storageKey
39
39
  }) {
40
- const { colorScheme, switchColorScheme } = useColorScheme.useColorScheme("system", { storageKey });
40
+ const { colorScheme, switchColorScheme } = useColorScheme.useColorScheme(void 0, { storageKey });
41
41
  return /* @__PURE__ */ jsxRuntime.jsx(
42
42
  Popover.Popover,
43
43
  {
@@ -14,7 +14,7 @@ const COLOR_SCHEME_OPTIONS = [
14
14
  function ColorSchemeMenuSection({
15
15
  storageKey
16
16
  } = {}) {
17
- const { colorScheme, switchColorScheme } = useColorScheme("system", { storageKey });
17
+ const { colorScheme, switchColorScheme } = useColorScheme(void 0, { storageKey });
18
18
  return /* @__PURE__ */ jsxs(Fragment, { children: [
19
19
  /* @__PURE__ */ jsx(Menu.Header, { children: "Udseende" }),
20
20
  COLOR_SCHEME_OPTIONS.map((option) => /* @__PURE__ */ jsx(
@@ -35,7 +35,7 @@ function ColorSchemeButton({
35
35
  variant = "outlined",
36
36
  storageKey
37
37
  }) {
38
- const { colorScheme, switchColorScheme } = useColorScheme("system", { storageKey });
38
+ const { colorScheme, switchColorScheme } = useColorScheme(void 0, { storageKey });
39
39
  return /* @__PURE__ */ jsx(
40
40
  Popover,
41
41
  {
@@ -22,6 +22,8 @@ const FileUpload = react.forwardRef(function FileUpload2({
22
22
  fullWidth = false,
23
23
  required,
24
24
  modified,
25
+ modifiedLabel,
26
+ labelAlignment,
25
27
  onChange,
26
28
  children,
27
29
  hint,
@@ -78,6 +80,8 @@ const FileUpload = react.forwardRef(function FileUpload2({
78
80
  fullWidth,
79
81
  required,
80
82
  modified,
83
+ modifiedLabel,
84
+ labelAlignment,
81
85
  children: /* @__PURE__ */ jsxRuntime.jsxs(
82
86
  "label",
83
87
  {
@@ -16,6 +16,8 @@ const FileUpload = forwardRef(function FileUpload2({
16
16
  fullWidth = false,
17
17
  required,
18
18
  modified,
19
+ modifiedLabel,
20
+ labelAlignment,
19
21
  onChange,
20
22
  children,
21
23
  hint,
@@ -72,6 +74,8 @@ const FileUpload = forwardRef(function FileUpload2({
72
74
  fullWidth,
73
75
  required,
74
76
  modified,
77
+ modifiedLabel,
78
+ labelAlignment,
75
79
  children: /* @__PURE__ */ jsxs(
76
80
  "label",
77
81
  {
@@ -67,6 +67,8 @@ function FormSelect({
67
67
  tooltip,
68
68
  tooltipPlacement = "right",
69
69
  modified = false,
70
+ modifiedLabel,
71
+ labelAlignment,
70
72
  // FormSelect props
71
73
  id,
72
74
  name,
@@ -130,6 +132,8 @@ function FormSelect({
130
132
  labelWidth,
131
133
  required,
132
134
  modified,
135
+ modifiedLabel,
136
+ labelAlignment,
133
137
  children: /* @__PURE__ */ jsxRuntime.jsxs(
134
138
  "div",
135
139
  {
@@ -31,5 +31,5 @@ export type FormSelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor
31
31
  */
32
32
  allowClearWhenRequired?: boolean;
33
33
  };
34
- export declare function FormSelect<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, helpTextAddition, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, name, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, includePlaceholderOption, allowClearWhenRequired, }: FormSelectProps<T>): React.ReactNode;
34
+ export declare function FormSelect<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, helpTextAddition, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, modifiedLabel, labelAlignment, id, name, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, includePlaceholderOption, allowClearWhenRequired, }: FormSelectProps<T>): React.ReactNode;
35
35
  export {};
@@ -42,6 +42,8 @@ function FormSelect({
42
42
  tooltip,
43
43
  tooltipPlacement = "right",
44
44
  modified = false,
45
+ modifiedLabel,
46
+ labelAlignment,
45
47
  // FormSelect props
46
48
  id,
47
49
  name,
@@ -105,6 +107,8 @@ function FormSelect({
105
107
  labelWidth,
106
108
  required,
107
109
  modified,
110
+ modifiedLabel,
111
+ labelAlignment,
108
112
  children: /* @__PURE__ */ jsxs(
109
113
  "div",
110
114
  {
@@ -36,6 +36,8 @@ const Input = react.forwardRef(function Input2({
36
36
  tooltip,
37
37
  tooltipPlacement = "right",
38
38
  modified = false,
39
+ modifiedLabel,
40
+ labelAlignment,
39
41
  icon,
40
42
  autoFocus,
41
43
  minWidth,
@@ -104,6 +106,8 @@ const Input = react.forwardRef(function Input2({
104
106
  labelWidth,
105
107
  required,
106
108
  modified,
109
+ modifiedLabel,
110
+ labelAlignment,
107
111
  children: /* @__PURE__ */ jsxRuntime.jsxs(
108
112
  "div",
109
113
  {
@@ -30,6 +30,8 @@ const Input = forwardRef(function Input2({
30
30
  tooltip,
31
31
  tooltipPlacement = "right",
32
32
  modified = false,
33
+ modifiedLabel,
34
+ labelAlignment,
33
35
  icon,
34
36
  autoFocus,
35
37
  minWidth,
@@ -98,6 +100,8 @@ const Input = forwardRef(function Input2({
98
100
  labelWidth,
99
101
  required,
100
102
  modified,
103
+ modifiedLabel,
104
+ labelAlignment,
101
105
  children: /* @__PURE__ */ jsxs(
102
106
  "div",
103
107
  {
@@ -4,8 +4,13 @@
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var react = require('react');
6
6
  var RadioButton = require('./RadioButton');
7
+ var styles = require('./RadioButtons.module.css');
7
8
  var InputContainer = require('../input-container/InputContainer');
8
9
 
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
13
+
9
14
  function RadioButtonGroup({
10
15
  name,
11
16
  value,
@@ -31,11 +36,7 @@ function RadioButtonGroup({
31
36
  role: "radiogroup",
32
37
  "aria-invalid": Boolean(error) || void 0,
33
38
  "data-cy": dataCy,
34
- style: {
35
- display: "flex",
36
- flexDirection: direction,
37
- gap: "8px"
38
- },
39
+ className: [styles__default.default.group, direction === "row" ? styles__default.default.groupRow : void 0].filter(Boolean).join(" "),
39
40
  children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
40
41
  RadioButton.RadioButton,
41
42
  {
@@ -2,6 +2,7 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { useId } from 'react';
4
4
  import { RadioButton } from './RadioButton';
5
+ import styles from './RadioButtons.module.css';
5
6
  import { InputContainer } from '../input-container/InputContainer';
6
7
 
7
8
  function RadioButtonGroup({
@@ -29,11 +30,7 @@ function RadioButtonGroup({
29
30
  role: "radiogroup",
30
31
  "aria-invalid": Boolean(error) || void 0,
31
32
  "data-cy": dataCy,
32
- style: {
33
- display: "flex",
34
- flexDirection: direction,
35
- gap: "8px"
36
- },
33
+ className: [styles.group, direction === "row" ? styles.groupRow : void 0].filter(Boolean).join(" "),
37
34
  children: options.map((opt) => /* @__PURE__ */ jsx(
38
35
  RadioButton,
39
36
  {
@@ -129,3 +129,18 @@
129
129
  width: 12px;
130
130
  height: 12px;
131
131
  }
132
+
133
+ .group {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: var(--spacing-sm);
137
+ }
138
+
139
+ .groupRow {
140
+ flex-direction: row;
141
+ flex-wrap: wrap;
142
+ }
143
+
144
+ .groupRow > * {
145
+ flex-shrink: 0;
146
+ }
@@ -29,6 +29,8 @@ function Select({
29
29
  tooltip,
30
30
  tooltipPlacement = "right",
31
31
  modified = false,
32
+ modifiedLabel,
33
+ labelAlignment,
32
34
  id,
33
35
  options,
34
36
  selectedValue,
@@ -199,6 +201,8 @@ function Select({
199
201
  labelWidth,
200
202
  required,
201
203
  modified,
204
+ modifiedLabel,
205
+ labelAlignment,
202
206
  children: /* @__PURE__ */ jsxRuntime.jsx(
203
207
  Popover.Popover,
204
208
  {
@@ -20,5 +20,5 @@ export type SelectProps<T> = Omit<InputContainerProps, 'children' | 'htmlFor' |
20
20
  tooltip?: React.ReactNode;
21
21
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
22
22
  };
23
- export declare function Select<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, helpTextAddition, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, minWidth, menuHeader, }: SelectProps<T>): React.ReactNode;
23
+ export declare function Select<T extends string | number | Record<string, any>>({ label, labelAction, error, helpText, helpTextAddition, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, modified, modifiedLabel, labelAlignment, id, options, selectedValue, onChange, placeholder, size, variant, onClear, datakey, dataCy, disabled, minWidth, menuHeader, }: SelectProps<T>): React.ReactNode;
24
24
  export {};
@@ -23,6 +23,8 @@ function Select({
23
23
  tooltip,
24
24
  tooltipPlacement = "right",
25
25
  modified = false,
26
+ modifiedLabel,
27
+ labelAlignment,
26
28
  id,
27
29
  options,
28
30
  selectedValue,
@@ -193,6 +195,8 @@ function Select({
193
195
  labelWidth,
194
196
  required,
195
197
  modified,
198
+ modifiedLabel,
199
+ labelAlignment,
196
200
  children: /* @__PURE__ */ jsx(
197
201
  Popover,
198
202
  {
@@ -26,6 +26,8 @@ const Textarea = function Textarea2({
26
26
  adjustHeight,
27
27
  id,
28
28
  modified = false,
29
+ modifiedLabel,
30
+ labelAlignment,
29
31
  // InputContainer props
30
32
  label,
31
33
  error,
@@ -97,6 +99,8 @@ const Textarea = function Textarea2({
97
99
  InputContainer.InputContainer,
98
100
  {
99
101
  modified,
102
+ modifiedLabel,
103
+ labelAlignment,
100
104
  label,
101
105
  htmlFor: textareaId,
102
106
  error,
@@ -20,6 +20,8 @@ const Textarea = function Textarea2({
20
20
  adjustHeight,
21
21
  id,
22
22
  modified = false,
23
+ modifiedLabel,
24
+ labelAlignment,
23
25
  // InputContainer props
24
26
  label,
25
27
  error,
@@ -91,6 +93,8 @@ const Textarea = function Textarea2({
91
93
  InputContainer,
92
94
  {
93
95
  modified,
96
+ modifiedLabel,
97
+ labelAlignment,
94
98
  label,
95
99
  htmlFor: textareaId,
96
100
  error,
@@ -32,6 +32,23 @@ function _interopNamespace(e) {
32
32
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
33
  var styles__default = /*#__PURE__*/_interopDefault(styles);
34
34
 
35
+ function orderByGroup(options) {
36
+ if (!options.some((option) => option.group)) return options;
37
+ const groups = /* @__PURE__ */ new Map();
38
+ for (const option of options) {
39
+ const group = option.group || void 0;
40
+ const entries = groups.get(group);
41
+ if (entries) entries.push(option);
42
+ else groups.set(group, [option]);
43
+ }
44
+ return Array.from(groups.values()).flat();
45
+ }
46
+ function findEnabledIndex(items, start, step) {
47
+ for (let index = start; index >= 0 && index < items.length; index += step) {
48
+ if (!items[index].disabled) return index;
49
+ }
50
+ return -1;
51
+ }
35
52
  function parseLengthToPx(value, referenceFontSize = 16) {
36
53
  if (!value) return 0;
37
54
  const trimmed = value.trim();
@@ -197,16 +214,25 @@ function Typeahead({
197
214
  filteredOptions = options;
198
215
  } else {
199
216
  filteredOptions = options.filter(
200
- (option) => option.label.toLocaleLowerCase().includes(normalizedQuery)
217
+ (option) => {
218
+ var _a2, _b, _c, _d;
219
+ return option.label.toLocaleLowerCase().includes(normalizedQuery) || ((_b = (_a2 = option.description) == null ? void 0 : _a2.toLocaleLowerCase().includes(normalizedQuery)) != null ? _b : false) || ((_d = (_c = option.group) == null ? void 0 : _c.toLocaleLowerCase().includes(normalizedQuery)) != null ? _d : false);
220
+ }
201
221
  );
202
222
  }
203
223
  }
224
+ filteredOptions = orderByGroup(filteredOptions);
204
225
  if (typeof maxVisibleOptions === "number" && Number.isFinite(maxVisibleOptions)) {
205
226
  filteredOptions = filteredOptions.slice(0, Math.max(0, maxVisibleOptions));
206
227
  }
228
+ const groupStarts = /* @__PURE__ */ new Map();
229
+ filteredOptions.forEach((option, index) => {
230
+ if (option.group && !groupStarts.has(option.group)) groupStarts.set(option.group, index);
231
+ });
207
232
  const commitSelection = React__namespace.useCallback(
208
233
  (option) => {
209
234
  var _a2, _b;
235
+ if (option == null ? void 0 : option.disabled) return;
210
236
  locallyClearedRef.current = false;
211
237
  setHideSelectedHighlight(false);
212
238
  if (mode === "multi") {
@@ -334,24 +360,21 @@ function Typeahead({
334
360
  }, [selectedOption, selectedSingleText, mode]);
335
361
  const getSelectedIndex = React__namespace.useCallback(
336
362
  (items) => {
337
- if (items.length === 0) return -1;
338
- if (locallyClearedRef.current) return 0;
339
- if (mode === "multi") {
340
- if (!Array.isArray(selectedValue) || selectedValue.length === 0) return 0;
341
- const firstSelectedIndex = items.findIndex((option) => selectedValue.includes(option.value));
342
- return firstSelectedIndex >= 0 ? firstSelectedIndex : 0;
343
- }
344
- const singleSelectedIndex = items.findIndex((option) => option.value === selectedValue);
345
- return singleSelectedIndex >= 0 ? singleSelectedIndex : 0;
363
+ const firstEnabled = findEnabledIndex(items, 0, 1);
364
+ if (firstEnabled < 0 || locallyClearedRef.current) return firstEnabled;
365
+ const selectedIndex = items.findIndex(
366
+ (option) => !option.disabled && (mode === "multi" ? Array.isArray(selectedValue) && selectedValue.includes(option.value) : option.value === selectedValue)
367
+ );
368
+ return selectedIndex >= 0 ? selectedIndex : firstEnabled;
346
369
  },
347
370
  [mode, selectedValue]
348
371
  );
349
372
  React.useEffect(() => {
350
373
  setActiveIndex((current) => {
351
- if (filteredOptions.length === 0) return -1;
352
- if (locallyClearedRef.current) return 0;
353
- if (current < 0) return getSelectedIndex(filteredOptions);
354
- return Math.min(current, filteredOptions.length - 1);
374
+ if (locallyClearedRef.current || current < 0 || !filteredOptions[current] || filteredOptions[current].disabled) {
375
+ return getSelectedIndex(filteredOptions);
376
+ }
377
+ return current;
355
378
  });
356
379
  }, [filteredOptions, getSelectedIndex]);
357
380
  React.useEffect(() => {
@@ -452,7 +475,14 @@ function Typeahead({
452
475
  }
453
476
  const exactLabelMatch = options.find((option) => option.label === inputValue);
454
477
  if (exactLabelMatch) {
455
- commitSelection(exactLabelMatch);
478
+ if (exactLabelMatch.disabled) {
479
+ setInputValue(selectedSingleText);
480
+ setQuery("");
481
+ setOpen(false);
482
+ setActiveIndex(-1);
483
+ } else {
484
+ commitSelection(exactLabelMatch);
485
+ }
456
486
  return;
457
487
  }
458
488
  if (allowCustomValue) {
@@ -484,7 +514,7 @@ function Typeahead({
484
514
  if (justClearedRef.current) return;
485
515
  setQuery("");
486
516
  setOpen(true);
487
- setActiveIndex(getSelectedIndex(options));
517
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
488
518
  }, [getSelectedIndex, options]);
489
519
  const openWithCurrentFilter = React__namespace.useCallback(() => {
490
520
  if (justClearedRef.current) return;
@@ -526,7 +556,7 @@ function Typeahead({
526
556
  if (isAlreadyFocused && mode === "single" && selectedOption && !locallyClearedRef.current) {
527
557
  prepareSingleSearchInput();
528
558
  setOpen(true);
529
- setActiveIndex(getSelectedIndex(options));
559
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
530
560
  } else if (isAlreadyFocused && !open) {
531
561
  setOpen(true);
532
562
  setActiveIndex(getSelectedIndex(filteredOptions));
@@ -573,7 +603,8 @@ function Typeahead({
573
603
  setActiveIndex((index) => {
574
604
  if (filteredOptions.length === 0) return -1;
575
605
  if (index < 0) return getSelectedIndex(filteredOptions);
576
- return Math.min(index + 1, filteredOptions.length - 1);
606
+ const next = findEnabledIndex(filteredOptions, index + 1, 1);
607
+ return next >= 0 ? next : index;
577
608
  });
578
609
  return;
579
610
  case "ArrowUp":
@@ -583,20 +614,23 @@ function Typeahead({
583
614
  return;
584
615
  }
585
616
  setActiveIndex((index) => {
586
- if (filteredOptions.length === 0) return -1;
587
- if (index < 0) return filteredOptions.length - 1;
588
- return Math.max(index - 1, 0);
617
+ const next = findEnabledIndex(
618
+ filteredOptions,
619
+ index < 0 ? filteredOptions.length - 1 : index - 1,
620
+ -1
621
+ );
622
+ return next >= 0 ? next : index;
589
623
  });
590
624
  return;
591
625
  case "Home":
592
626
  if (!open || filteredOptions.length === 0) return;
593
627
  e.preventDefault();
594
- setActiveIndex(0);
628
+ setActiveIndex(findEnabledIndex(filteredOptions, 0, 1));
595
629
  return;
596
630
  case "End":
597
631
  if (!open || filteredOptions.length === 0) return;
598
632
  e.preventDefault();
599
- setActiveIndex(filteredOptions.length - 1);
633
+ setActiveIndex(findEnabledIndex(filteredOptions, filteredOptions.length - 1, -1));
600
634
  return;
601
635
  case "Enter":
602
636
  if (!open) return;
@@ -650,7 +684,7 @@ function Typeahead({
650
684
  if (nextOpen) {
651
685
  if (mode === "single" && selectedOption && !locallyClearedRef.current) {
652
686
  setQuery("");
653
- setActiveIndex(getSelectedIndex(options));
687
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
654
688
  } else {
655
689
  setActiveIndex(getSelectedIndex(filteredOptions));
656
690
  }
@@ -768,41 +802,74 @@ function Typeahead({
768
802
  const isActive = index === activeIndex;
769
803
  const isSelected = mode === "multi" ? Array.isArray(selectedValue) && selectedValue.includes(option.value) : option.value === selectedValue && !hideSelectedHighlight;
770
804
  const optionId = `${listboxId}-option-${index}`;
771
- return mode === "multi" ? /* @__PURE__ */ jsxRuntime.jsx(
805
+ const groupId = option.group ? `${listboxId}-group-${groupStarts.get(option.group)}` : void 0;
806
+ const startsGroup = !!option.group && groupStarts.get(option.group) === index;
807
+ const descriptionId = option.description ? `${optionId}-description` : void 0;
808
+ const describedBy = [groupId, descriptionId].filter(Boolean).join(" ") || void 0;
809
+ const labelContent = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.optionText, children: [
810
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: option.label }),
811
+ option.description && /* @__PURE__ */ jsxRuntime.jsx("span", { id: descriptionId, className: styles__default.default.description, children: option.description })
812
+ ] });
813
+ const item = mode === "multi" ? /* @__PURE__ */ jsxRuntime.jsx(
772
814
  Menu.Menu.CheckItem,
773
815
  {
774
816
  checked: isSelected,
817
+ disabled: option.disabled,
775
818
  active: isActive,
776
819
  interactiveProps: {
777
820
  id: optionId,
778
821
  role: "option",
779
- onMouseEnter: () => setActiveIndex(index),
822
+ "aria-describedby": describedBy,
823
+ "aria-label": option.label,
824
+ "aria-disabled": option.disabled || void 0,
825
+ onMouseEnter: () => {
826
+ if (!option.disabled) setActiveIndex(index);
827
+ },
780
828
  onMouseDown: (e) => {
781
829
  interactingWithOptionsRef.current = true;
782
830
  e.preventDefault();
783
831
  },
784
832
  onKeyDown: (e) => handleTabKeyDown(e)
785
833
  },
786
- label: /* @__PURE__ */ jsxRuntime.jsx("span", { children: option.label }),
834
+ label: labelContent,
787
835
  onCheckedChange: () => commitSelection(option)
788
836
  },
789
837
  option.value
790
- ) : /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Item, { active: isActive, selected: isSelected, children: /* @__PURE__ */ jsxRuntime.jsx(
791
- "button",
838
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
839
+ Menu.Menu.Item,
792
840
  {
793
- id: optionId,
794
- type: "button",
795
- role: "option",
796
- "aria-selected": isSelected,
797
- onMouseEnter: () => setActiveIndex(index),
798
- onMouseDown: (e) => {
799
- e.preventDefault();
800
- },
801
- onKeyDown: (e) => handleTabKeyDown(e),
802
- onClick: () => commitSelection(option),
803
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: option.label })
804
- }
805
- ) }, option.value);
841
+ active: isActive,
842
+ selected: isSelected,
843
+ disabled: option.disabled,
844
+ children: /* @__PURE__ */ jsxRuntime.jsx(
845
+ "button",
846
+ {
847
+ id: optionId,
848
+ type: "button",
849
+ role: "option",
850
+ "aria-selected": isSelected,
851
+ "aria-describedby": describedBy,
852
+ "aria-label": option.label,
853
+ "aria-disabled": option.disabled || void 0,
854
+ disabled: option.disabled,
855
+ onMouseEnter: () => {
856
+ if (!option.disabled) setActiveIndex(index);
857
+ },
858
+ onMouseDown: (e) => {
859
+ e.preventDefault();
860
+ },
861
+ onKeyDown: (e) => handleTabKeyDown(e),
862
+ onClick: () => commitSelection(option),
863
+ children: labelContent
864
+ }
865
+ )
866
+ },
867
+ option.value
868
+ );
869
+ return /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
870
+ startsGroup && /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { id: groupId, children: option.group }),
871
+ item
872
+ ] }, option.value);
806
873
  }) : /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Item, { disabled: true, children: emptyMessage }) })
807
874
  }
808
875
  ),
@@ -3,6 +3,12 @@ import { InputProps, InputVariant } from '../../../components/forms/input/Input'
3
3
  type TypeaheadOption<T> = {
4
4
  label: string;
5
5
  value: T;
6
+ /** Optional section header; groups follow their first occurrence in the list. */
7
+ group?: string;
8
+ /** Secondary option text, included in default search but not the selected input value. */
9
+ description?: string;
10
+ /** Visible but unavailable for selection by pointer, keyboard, or exact-label commit. */
11
+ disabled?: boolean;
6
12
  };
7
13
  type MultiValueDisplayMode = 'chips' | 'count';
8
14
  type MultiSelectedValuesDisplayMode = 'hidden' | 'below-input';