@dbcdk/react-components 0.0.210 → 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 (61) 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/nav-bar/NavBar.d.ts +2 -1
  32. package/dist/components/sidebar/Sidebar.cjs +2 -1
  33. package/dist/components/sidebar/Sidebar.js +2 -1
  34. package/dist/components/sidebar/components/SidebarItem.cjs +36 -13
  35. package/dist/components/sidebar/components/SidebarItem.d.ts +5 -2
  36. package/dist/components/sidebar/components/SidebarItem.js +32 -13
  37. package/dist/components/sidebar/components/SidebarItem.module.css +16 -0
  38. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.cjs +43 -116
  39. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  40. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +44 -117
  41. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +17 -25
  42. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +40 -1
  43. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.cjs +3 -2
  44. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +2 -2
  45. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +3 -2
  46. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +13 -10
  47. package/dist/components/sidebar/components/sidebar-items/SidebarItems.cjs +16 -22
  48. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -22
  49. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +0 -1
  50. package/dist/components/sidebar/providers/SidebarProvider.cjs +9 -7
  51. package/dist/components/sidebar/providers/SidebarProvider.d.ts +2 -1
  52. package/dist/components/sidebar/providers/SidebarProvider.js +9 -7
  53. package/dist/components/theme-script/ThemeScript.cjs +1 -1
  54. package/dist/components/theme-script/ThemeScript.d.ts +1 -1
  55. package/dist/components/theme-script/ThemeScript.js +1 -1
  56. package/dist/hooks/useColorScheme.cjs +1 -1
  57. package/dist/hooks/useColorScheme.js +1 -1
  58. package/dist/hooks/useGetActiveTheme.cjs +1 -1
  59. package/dist/hooks/useGetActiveTheme.js +1 -1
  60. package/dist/styles/themes/dbc/colors.css +4 -4
  61. package/package.json +1 -1
@@ -8,6 +8,23 @@ import { Menu } from '../../../components/menu/Menu';
8
8
  import { Popover } from '../../../components/popover/Popover';
9
9
  import styles from './Typeahead.module.css';
10
10
 
11
+ function orderByGroup(options) {
12
+ if (!options.some((option) => option.group)) return options;
13
+ const groups = /* @__PURE__ */ new Map();
14
+ for (const option of options) {
15
+ const group = option.group || void 0;
16
+ const entries = groups.get(group);
17
+ if (entries) entries.push(option);
18
+ else groups.set(group, [option]);
19
+ }
20
+ return Array.from(groups.values()).flat();
21
+ }
22
+ function findEnabledIndex(items, start, step) {
23
+ for (let index = start; index >= 0 && index < items.length; index += step) {
24
+ if (!items[index].disabled) return index;
25
+ }
26
+ return -1;
27
+ }
11
28
  function parseLengthToPx(value, referenceFontSize = 16) {
12
29
  if (!value) return 0;
13
30
  const trimmed = value.trim();
@@ -173,16 +190,25 @@ function Typeahead({
173
190
  filteredOptions = options;
174
191
  } else {
175
192
  filteredOptions = options.filter(
176
- (option) => option.label.toLocaleLowerCase().includes(normalizedQuery)
193
+ (option) => {
194
+ var _a2, _b, _c, _d;
195
+ 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);
196
+ }
177
197
  );
178
198
  }
179
199
  }
200
+ filteredOptions = orderByGroup(filteredOptions);
180
201
  if (typeof maxVisibleOptions === "number" && Number.isFinite(maxVisibleOptions)) {
181
202
  filteredOptions = filteredOptions.slice(0, Math.max(0, maxVisibleOptions));
182
203
  }
204
+ const groupStarts = /* @__PURE__ */ new Map();
205
+ filteredOptions.forEach((option, index) => {
206
+ if (option.group && !groupStarts.has(option.group)) groupStarts.set(option.group, index);
207
+ });
183
208
  const commitSelection = React.useCallback(
184
209
  (option) => {
185
210
  var _a2, _b;
211
+ if (option == null ? void 0 : option.disabled) return;
186
212
  locallyClearedRef.current = false;
187
213
  setHideSelectedHighlight(false);
188
214
  if (mode === "multi") {
@@ -310,24 +336,21 @@ function Typeahead({
310
336
  }, [selectedOption, selectedSingleText, mode]);
311
337
  const getSelectedIndex = React.useCallback(
312
338
  (items) => {
313
- if (items.length === 0) return -1;
314
- if (locallyClearedRef.current) return 0;
315
- if (mode === "multi") {
316
- if (!Array.isArray(selectedValue) || selectedValue.length === 0) return 0;
317
- const firstSelectedIndex = items.findIndex((option) => selectedValue.includes(option.value));
318
- return firstSelectedIndex >= 0 ? firstSelectedIndex : 0;
319
- }
320
- const singleSelectedIndex = items.findIndex((option) => option.value === selectedValue);
321
- return singleSelectedIndex >= 0 ? singleSelectedIndex : 0;
339
+ const firstEnabled = findEnabledIndex(items, 0, 1);
340
+ if (firstEnabled < 0 || locallyClearedRef.current) return firstEnabled;
341
+ const selectedIndex = items.findIndex(
342
+ (option) => !option.disabled && (mode === "multi" ? Array.isArray(selectedValue) && selectedValue.includes(option.value) : option.value === selectedValue)
343
+ );
344
+ return selectedIndex >= 0 ? selectedIndex : firstEnabled;
322
345
  },
323
346
  [mode, selectedValue]
324
347
  );
325
348
  useEffect(() => {
326
349
  setActiveIndex((current) => {
327
- if (filteredOptions.length === 0) return -1;
328
- if (locallyClearedRef.current) return 0;
329
- if (current < 0) return getSelectedIndex(filteredOptions);
330
- return Math.min(current, filteredOptions.length - 1);
350
+ if (locallyClearedRef.current || current < 0 || !filteredOptions[current] || filteredOptions[current].disabled) {
351
+ return getSelectedIndex(filteredOptions);
352
+ }
353
+ return current;
331
354
  });
332
355
  }, [filteredOptions, getSelectedIndex]);
333
356
  useEffect(() => {
@@ -428,7 +451,14 @@ function Typeahead({
428
451
  }
429
452
  const exactLabelMatch = options.find((option) => option.label === inputValue);
430
453
  if (exactLabelMatch) {
431
- commitSelection(exactLabelMatch);
454
+ if (exactLabelMatch.disabled) {
455
+ setInputValue(selectedSingleText);
456
+ setQuery("");
457
+ setOpen(false);
458
+ setActiveIndex(-1);
459
+ } else {
460
+ commitSelection(exactLabelMatch);
461
+ }
432
462
  return;
433
463
  }
434
464
  if (allowCustomValue) {
@@ -460,7 +490,7 @@ function Typeahead({
460
490
  if (justClearedRef.current) return;
461
491
  setQuery("");
462
492
  setOpen(true);
463
- setActiveIndex(getSelectedIndex(options));
493
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
464
494
  }, [getSelectedIndex, options]);
465
495
  const openWithCurrentFilter = React.useCallback(() => {
466
496
  if (justClearedRef.current) return;
@@ -502,7 +532,7 @@ function Typeahead({
502
532
  if (isAlreadyFocused && mode === "single" && selectedOption && !locallyClearedRef.current) {
503
533
  prepareSingleSearchInput();
504
534
  setOpen(true);
505
- setActiveIndex(getSelectedIndex(options));
535
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
506
536
  } else if (isAlreadyFocused && !open) {
507
537
  setOpen(true);
508
538
  setActiveIndex(getSelectedIndex(filteredOptions));
@@ -549,7 +579,8 @@ function Typeahead({
549
579
  setActiveIndex((index) => {
550
580
  if (filteredOptions.length === 0) return -1;
551
581
  if (index < 0) return getSelectedIndex(filteredOptions);
552
- return Math.min(index + 1, filteredOptions.length - 1);
582
+ const next = findEnabledIndex(filteredOptions, index + 1, 1);
583
+ return next >= 0 ? next : index;
553
584
  });
554
585
  return;
555
586
  case "ArrowUp":
@@ -559,20 +590,23 @@ function Typeahead({
559
590
  return;
560
591
  }
561
592
  setActiveIndex((index) => {
562
- if (filteredOptions.length === 0) return -1;
563
- if (index < 0) return filteredOptions.length - 1;
564
- return Math.max(index - 1, 0);
593
+ const next = findEnabledIndex(
594
+ filteredOptions,
595
+ index < 0 ? filteredOptions.length - 1 : index - 1,
596
+ -1
597
+ );
598
+ return next >= 0 ? next : index;
565
599
  });
566
600
  return;
567
601
  case "Home":
568
602
  if (!open || filteredOptions.length === 0) return;
569
603
  e.preventDefault();
570
- setActiveIndex(0);
604
+ setActiveIndex(findEnabledIndex(filteredOptions, 0, 1));
571
605
  return;
572
606
  case "End":
573
607
  if (!open || filteredOptions.length === 0) return;
574
608
  e.preventDefault();
575
- setActiveIndex(filteredOptions.length - 1);
609
+ setActiveIndex(findEnabledIndex(filteredOptions, filteredOptions.length - 1, -1));
576
610
  return;
577
611
  case "Enter":
578
612
  if (!open) return;
@@ -626,7 +660,7 @@ function Typeahead({
626
660
  if (nextOpen) {
627
661
  if (mode === "single" && selectedOption && !locallyClearedRef.current) {
628
662
  setQuery("");
629
- setActiveIndex(getSelectedIndex(options));
663
+ setActiveIndex(getSelectedIndex(orderByGroup(options)));
630
664
  } else {
631
665
  setActiveIndex(getSelectedIndex(filteredOptions));
632
666
  }
@@ -744,41 +778,74 @@ function Typeahead({
744
778
  const isActive = index === activeIndex;
745
779
  const isSelected = mode === "multi" ? Array.isArray(selectedValue) && selectedValue.includes(option.value) : option.value === selectedValue && !hideSelectedHighlight;
746
780
  const optionId = `${listboxId}-option-${index}`;
747
- return mode === "multi" ? /* @__PURE__ */ jsx(
781
+ const groupId = option.group ? `${listboxId}-group-${groupStarts.get(option.group)}` : void 0;
782
+ const startsGroup = !!option.group && groupStarts.get(option.group) === index;
783
+ const descriptionId = option.description ? `${optionId}-description` : void 0;
784
+ const describedBy = [groupId, descriptionId].filter(Boolean).join(" ") || void 0;
785
+ const labelContent = /* @__PURE__ */ jsxs("span", { className: styles.optionText, children: [
786
+ /* @__PURE__ */ jsx("span", { children: option.label }),
787
+ option.description && /* @__PURE__ */ jsx("span", { id: descriptionId, className: styles.description, children: option.description })
788
+ ] });
789
+ const item = mode === "multi" ? /* @__PURE__ */ jsx(
748
790
  Menu.CheckItem,
749
791
  {
750
792
  checked: isSelected,
793
+ disabled: option.disabled,
751
794
  active: isActive,
752
795
  interactiveProps: {
753
796
  id: optionId,
754
797
  role: "option",
755
- onMouseEnter: () => setActiveIndex(index),
798
+ "aria-describedby": describedBy,
799
+ "aria-label": option.label,
800
+ "aria-disabled": option.disabled || void 0,
801
+ onMouseEnter: () => {
802
+ if (!option.disabled) setActiveIndex(index);
803
+ },
756
804
  onMouseDown: (e) => {
757
805
  interactingWithOptionsRef.current = true;
758
806
  e.preventDefault();
759
807
  },
760
808
  onKeyDown: (e) => handleTabKeyDown(e)
761
809
  },
762
- label: /* @__PURE__ */ jsx("span", { children: option.label }),
810
+ label: labelContent,
763
811
  onCheckedChange: () => commitSelection(option)
764
812
  },
765
813
  option.value
766
- ) : /* @__PURE__ */ jsx(Menu.Item, { active: isActive, selected: isSelected, children: /* @__PURE__ */ jsx(
767
- "button",
814
+ ) : /* @__PURE__ */ jsx(
815
+ Menu.Item,
768
816
  {
769
- id: optionId,
770
- type: "button",
771
- role: "option",
772
- "aria-selected": isSelected,
773
- onMouseEnter: () => setActiveIndex(index),
774
- onMouseDown: (e) => {
775
- e.preventDefault();
776
- },
777
- onKeyDown: (e) => handleTabKeyDown(e),
778
- onClick: () => commitSelection(option),
779
- children: /* @__PURE__ */ jsx("span", { children: option.label })
780
- }
781
- ) }, option.value);
817
+ active: isActive,
818
+ selected: isSelected,
819
+ disabled: option.disabled,
820
+ children: /* @__PURE__ */ jsx(
821
+ "button",
822
+ {
823
+ id: optionId,
824
+ type: "button",
825
+ role: "option",
826
+ "aria-selected": isSelected,
827
+ "aria-describedby": describedBy,
828
+ "aria-label": option.label,
829
+ "aria-disabled": option.disabled || void 0,
830
+ disabled: option.disabled,
831
+ onMouseEnter: () => {
832
+ if (!option.disabled) setActiveIndex(index);
833
+ },
834
+ onMouseDown: (e) => {
835
+ e.preventDefault();
836
+ },
837
+ onKeyDown: (e) => handleTabKeyDown(e),
838
+ onClick: () => commitSelection(option),
839
+ children: labelContent
840
+ }
841
+ )
842
+ },
843
+ option.value
844
+ );
845
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
846
+ startsGroup && /* @__PURE__ */ jsx(Menu.Header, { id: groupId, children: option.group }),
847
+ item
848
+ ] }, option.value);
782
849
  }) : /* @__PURE__ */ jsx(Menu.Item, { disabled: true, children: emptyMessage }) })
783
850
  }
784
851
  ),
@@ -36,3 +36,19 @@
36
36
  .typeaheadInput {
37
37
  cursor: pointer;
38
38
  }
39
+
40
+ /* Label and description share one row; the description wraps below only when space runs out. */
41
+ .optionText {
42
+ display: flex;
43
+ flex-wrap: wrap;
44
+ align-items: baseline;
45
+ column-gap: var(--spacing-md);
46
+ min-width: 0;
47
+ }
48
+
49
+ .description {
50
+ margin-inline-start: auto;
51
+ font-size: var(--font-size-xs);
52
+ color: var(--color-fg-muted);
53
+ text-align: end;
54
+ }
@@ -7,7 +7,6 @@ var react = require('react');
7
7
  var usePersistentState = require('../../hooks/usePersistentState');
8
8
  var Headline = require('./Headline');
9
9
  var styles = require('./Headline.module.css');
10
- var Button = require('../button/Button');
11
10
 
12
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
12
 
@@ -45,7 +44,7 @@ function CollapsibleHeadline({
45
44
  onToggle == null ? void 0 : onToggle();
46
45
  };
47
46
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.collapsibleRoot, children: [
48
- /* @__PURE__ */ jsxRuntime.jsxs(
47
+ /* @__PURE__ */ jsxRuntime.jsx(
49
48
  Headline.Headline,
50
49
  {
51
50
  ...headlineProps,
@@ -53,28 +52,26 @@ function CollapsibleHeadline({
53
52
  weight,
54
53
  size,
55
54
  addition,
56
- children: [
57
- header,
58
- /* @__PURE__ */ jsxRuntime.jsx(
59
- Button.Button,
60
- {
61
- shape: "round",
62
- type: "button",
63
- variant: "inline",
64
- style: { padding: "var(--spacing-2xs)" },
65
- "aria-expanded": isExpanded,
66
- "aria-controls": panelId,
67
- onClick: handleToggle,
68
- children: /* @__PURE__ */ jsxRuntime.jsx(
55
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
56
+ "button",
57
+ {
58
+ type: "button",
59
+ className: styles__default.default.toggleButton,
60
+ "aria-expanded": isExpanded,
61
+ "aria-controls": panelId,
62
+ onClick: handleToggle,
63
+ children: [
64
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: header }),
65
+ /* @__PURE__ */ jsxRuntime.jsx(
69
66
  lucideReact.ChevronDown,
70
67
  {
71
68
  "aria-hidden": true,
72
69
  className: [styles__default.default.toggleChevron, isExpanded ? styles__default.default.toggleChevronExpanded : ""].filter(Boolean).join(" ")
73
70
  }
74
71
  )
75
- }
76
- )
77
- ]
72
+ ]
73
+ }
74
+ )
78
75
  }
79
76
  ),
80
77
  isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { id: panelId, className: styles__default.default.collapsiblePanel, children })
@@ -1,7 +1,7 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
2
  import type { HeadlineProps } from './Headline';
3
3
  export interface CollapsibleHeadlineProps extends Omit<HeadlineProps, 'addition' | 'children'> {
4
- /** The headline text — always visible. */
4
+ /** The headline text — always visible. Use addition for separate interactive content. */
5
5
  header: React.ReactNode;
6
6
  /** Controlled expanded state. When omitted, the component manages its own state. */
7
7
  expanded?: boolean;
@@ -9,7 +9,7 @@ export interface CollapsibleHeadlineProps extends Omit<HeadlineProps, 'addition'
9
9
  onToggle?: () => void;
10
10
  /** id of the panel element, used for aria-controls. Generated automatically if omitted. */
11
11
  controls?: string;
12
- /** Extra content rendered between the headline text and the toggle button. */
12
+ /** Extra content rendered beside the heading, outside the toggle button. */
13
13
  addition?: React.ReactNode;
14
14
  /**
15
15
  * When set in uncontrolled mode, the component persists its expanded
@@ -5,7 +5,6 @@ import { useId } from 'react';
5
5
  import { usePersistentState } from '../../hooks/usePersistentState';
6
6
  import { Headline } from './Headline';
7
7
  import styles from './Headline.module.css';
8
- import { Button } from '../button/Button';
9
8
 
10
9
  function CollapsibleHeadline({
11
10
  header,
@@ -39,7 +38,7 @@ function CollapsibleHeadline({
39
38
  onToggle == null ? void 0 : onToggle();
40
39
  };
41
40
  return /* @__PURE__ */ jsxs("div", { className: styles.collapsibleRoot, children: [
42
- /* @__PURE__ */ jsxs(
41
+ /* @__PURE__ */ jsx(
43
42
  Headline,
44
43
  {
45
44
  ...headlineProps,
@@ -47,28 +46,26 @@ function CollapsibleHeadline({
47
46
  weight,
48
47
  size,
49
48
  addition,
50
- children: [
51
- header,
52
- /* @__PURE__ */ jsx(
53
- Button,
54
- {
55
- shape: "round",
56
- type: "button",
57
- variant: "inline",
58
- style: { padding: "var(--spacing-2xs)" },
59
- "aria-expanded": isExpanded,
60
- "aria-controls": panelId,
61
- onClick: handleToggle,
62
- children: /* @__PURE__ */ jsx(
49
+ children: /* @__PURE__ */ jsxs(
50
+ "button",
51
+ {
52
+ type: "button",
53
+ className: styles.toggleButton,
54
+ "aria-expanded": isExpanded,
55
+ "aria-controls": panelId,
56
+ onClick: handleToggle,
57
+ children: [
58
+ /* @__PURE__ */ jsx("span", { children: header }),
59
+ /* @__PURE__ */ jsx(
63
60
  ChevronDown,
64
61
  {
65
62
  "aria-hidden": true,
66
63
  className: [styles.toggleChevron, isExpanded ? styles.toggleChevronExpanded : ""].filter(Boolean).join(" ")
67
64
  }
68
65
  )
69
- }
70
- )
71
- ]
66
+ ]
67
+ }
68
+ )
72
69
  }
73
70
  ),
74
71
  isExpanded && /* @__PURE__ */ jsx("div", { id: panelId, className: styles.collapsiblePanel, children })
@@ -47,6 +47,27 @@
47
47
  }
48
48
 
49
49
  /* CollapsibleHeadline wrapper */
50
+ .toggleButton {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: var(--spacing-xs);
54
+ min-width: 0;
55
+ padding: 0;
56
+ border: 0;
57
+ background: none;
58
+ color: inherit;
59
+ font: inherit;
60
+ letter-spacing: inherit;
61
+ text-align: start;
62
+ cursor: pointer;
63
+ border-radius: var(--border-radius-sm);
64
+ }
65
+
66
+ .toggleButton:focus-visible {
67
+ outline: var(--border-width-medium) solid var(--color-border-selected);
68
+ outline-offset: var(--spacing-2xs);
69
+ }
70
+
50
71
  .collapsibleRoot {
51
72
  display: flex;
52
73
  flex-direction: column;
@@ -58,6 +79,7 @@
58
79
 
59
80
  .toggleChevron {
60
81
  display: block;
82
+ flex-shrink: 0;
61
83
  width: 0.85em;
62
84
  height: 0.85em;
63
85
  transition: transform var(--transition-normal) var(--ease-standard);
@@ -18,7 +18,8 @@ export type NavBarLinkItem = NavBarBase & {
18
18
  active?: boolean;
19
19
  external?: boolean;
20
20
  };
21
- /** Clickable + expandable item (has href AND children) */
21
+ /** Expandable container. In Sidebar, href is a stable expansion key, not a destination.
22
+ * Legacy component/active/external props are accepted but ignored by Sidebar groups. */
22
23
  export type NavBarExpandableItem = NavBarBase & {
23
24
  type: 'expandable';
24
25
  href: string;
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var jsxRuntime = require('react/jsx-runtime');
@@ -19,7 +20,7 @@ function Sidebar({
19
20
  minWidth,
20
21
  storageKey
21
22
  }) {
22
- return /* @__PURE__ */ jsxRuntime.jsx(SidebarProvider.SidebarProvider, { items, children: /* @__PURE__ */ jsxRuntime.jsx(
23
+ return /* @__PURE__ */ jsxRuntime.jsx(SidebarProvider.SidebarProvider, { items, initialActiveLink: activeLink, children: /* @__PURE__ */ jsxRuntime.jsx(
23
24
  SidebarContainer.SidebarContainer,
24
25
  {
25
26
  productName,
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx } from 'react/jsx-runtime';
2
3
  import { SidebarContainer } from './components/sidebar-container/SidebarContainer';
3
4
  import { SidebarProvider } from './providers/SidebarProvider';
@@ -17,7 +18,7 @@ function Sidebar({
17
18
  minWidth,
18
19
  storageKey
19
20
  }) {
20
- return /* @__PURE__ */ jsx(SidebarProvider, { items, children: /* @__PURE__ */ jsx(
21
+ return /* @__PURE__ */ jsx(SidebarProvider, { items, initialActiveLink: activeLink, children: /* @__PURE__ */ jsx(
21
22
  SidebarContainer,
22
23
  {
23
24
  productName,
@@ -1,31 +1,54 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var jsxRuntime = require('react/jsx-runtime');
5
+ var styles = require('./SidebarItem.module.css');
6
+ var SidebarProvider = require('../providers/SidebarProvider');
4
7
  var SidebarItemContent = require('./sidebar-item-content/SidebarItemContent');
5
8
 
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
12
+
6
13
  function SidebarItem({
7
- component: Component,
14
+ component: Component = "a",
8
15
  label,
9
16
  icon,
10
17
  badge,
11
18
  href,
12
19
  iconWidth,
13
- truncateLabel
20
+ depth = 0,
21
+ truncateLabel,
22
+ enabled = true,
23
+ external
14
24
  }) {
15
- if (!Component) {
16
- return null;
17
- }
18
- return /* @__PURE__ */ jsxRuntime.jsx(Component, { children: /* @__PURE__ */ jsxRuntime.jsx(
19
- SidebarItemContent.SidebarItemContent,
25
+ const { activeLink, isSidebarCollapsed } = SidebarProvider.useSidebar();
26
+ return /* @__PURE__ */ jsxRuntime.jsx(
27
+ Component,
20
28
  {
21
- icon,
22
- badge,
23
- iconWidth,
24
- label,
25
29
  href,
26
- truncateLabel
30
+ className: styles__default.default.link,
31
+ "aria-label": isSidebarCollapsed ? label : void 0,
32
+ "aria-current": enabled && activeLink === href ? "page" : void 0,
33
+ "aria-disabled": !enabled || void 0,
34
+ tabIndex: enabled ? void 0 : -1,
35
+ onClick: enabled ? void 0 : (event) => event.preventDefault(),
36
+ ...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
37
+ children: /* @__PURE__ */ jsxRuntime.jsx(
38
+ SidebarItemContent.SidebarItemContent,
39
+ {
40
+ icon,
41
+ badge,
42
+ depth,
43
+ iconWidth,
44
+ label,
45
+ href,
46
+ disableActiveStyles: !enabled,
47
+ truncateLabel
48
+ }
49
+ )
27
50
  }
28
- ) });
51
+ );
29
52
  }
30
53
 
31
54
  exports.SidebarItem = SidebarItem;
@@ -2,11 +2,14 @@ import React from 'react';
2
2
  interface SidebarItemProps {
3
3
  component?: React.ElementType;
4
4
  label: string;
5
- icon: React.ReactNode;
5
+ icon?: React.ReactNode;
6
6
  badge?: number | string;
7
7
  href?: string;
8
+ depth?: number;
8
9
  iconWidth?: string | number;
10
+ enabled?: boolean;
11
+ external?: boolean;
9
12
  truncateLabel?: boolean;
10
13
  }
11
- export declare function SidebarItem({ component: Component, label, icon, badge, href, iconWidth, truncateLabel, }: SidebarItemProps): React.ReactNode;
14
+ export declare function SidebarItem({ component: Component, label, icon, badge, href, iconWidth, depth, truncateLabel, enabled, external, }: SidebarItemProps): React.ReactNode;
12
15
  export {};
@@ -1,29 +1,48 @@
1
+ 'use client';
1
2
  import { jsx } from 'react/jsx-runtime';
3
+ import styles from './SidebarItem.module.css';
4
+ import { useSidebar } from '../providers/SidebarProvider';
2
5
  import { SidebarItemContent } from './sidebar-item-content/SidebarItemContent';
3
6
 
4
7
  function SidebarItem({
5
- component: Component,
8
+ component: Component = "a",
6
9
  label,
7
10
  icon,
8
11
  badge,
9
12
  href,
10
13
  iconWidth,
11
- truncateLabel
14
+ depth = 0,
15
+ truncateLabel,
16
+ enabled = true,
17
+ external
12
18
  }) {
13
- if (!Component) {
14
- return null;
15
- }
16
- return /* @__PURE__ */ jsx(Component, { children: /* @__PURE__ */ jsx(
17
- SidebarItemContent,
19
+ const { activeLink, isSidebarCollapsed } = useSidebar();
20
+ return /* @__PURE__ */ jsx(
21
+ Component,
18
22
  {
19
- icon,
20
- badge,
21
- iconWidth,
22
- label,
23
23
  href,
24
- truncateLabel
24
+ className: styles.link,
25
+ "aria-label": isSidebarCollapsed ? label : void 0,
26
+ "aria-current": enabled && activeLink === href ? "page" : void 0,
27
+ "aria-disabled": !enabled || void 0,
28
+ tabIndex: enabled ? void 0 : -1,
29
+ onClick: enabled ? void 0 : (event) => event.preventDefault(),
30
+ ...external ? { target: "_blank", rel: "noopener noreferrer" } : {},
31
+ children: /* @__PURE__ */ jsx(
32
+ SidebarItemContent,
33
+ {
34
+ icon,
35
+ badge,
36
+ depth,
37
+ iconWidth,
38
+ label,
39
+ href,
40
+ disableActiveStyles: !enabled,
41
+ truncateLabel
42
+ }
43
+ )
25
44
  }
26
- ) });
45
+ );
27
46
  }
28
47
 
29
48
  export { SidebarItem };
@@ -0,0 +1,16 @@
1
+ .link {
2
+ display: block;
3
+ color: inherit;
4
+ text-decoration: none;
5
+ border-radius: var(--border-radius-md);
6
+ }
7
+
8
+ .link:focus-visible {
9
+ outline: none;
10
+ box-shadow: var(--focus-ring);
11
+ }
12
+
13
+ .link[aria-disabled='true'] {
14
+ color: var(--color-fg-subtle);
15
+ cursor: default;
16
+ }