@broberg/app-shell 0.11.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/preact.d.cts CHANGED
@@ -16,10 +16,45 @@ interface NavItem<Icon = unknown> {
16
16
  * it is on the current path.
17
17
  */
18
18
  children?: NavItem<Icon>[];
19
+ /** F092.20 — a status marker before the label; "working" = a run in progress. */
20
+ marker?: "working";
21
+ /** F092.20 — shows a pin icon after the label. */
22
+ pinned?: boolean;
23
+ /** F092.21 — row actions: a «…» button opens a menu with these. */
24
+ actions?: NavAction[];
25
+ }
26
+ /**
27
+ * F092.21 — one row action. `submenu` switches the menu to a sub-list (with a
28
+ * back arrow); `input` switches it to a field + save (e.g. rename). A plain
29
+ * action, a submenu leaf and a saved input all call `onItemAction`.
30
+ */
31
+ interface NavAction {
32
+ id: string;
33
+ label: string;
34
+ icon?: unknown;
35
+ /** A single letter that picks this action while the menu is open (e.g. "P"). */
36
+ kbd?: string;
37
+ /** Shown in the danger colour (delete). */
38
+ danger?: boolean;
39
+ submenu?: NavAction[];
40
+ input?: {
41
+ value: string;
42
+ placeholder?: string;
43
+ saveLabel?: string;
44
+ };
45
+ }
46
+ interface NavSection<Icon = unknown> {
47
+ /** A sub-heading inside one group, e.g. "I dag" / "I går". */
48
+ label: string;
49
+ items: NavItem<Icon>[];
19
50
  }
20
51
  interface NavGroup<Icon = unknown> {
21
52
  label: string;
22
53
  items: NavItem<Icon>[];
54
+ /** F092.20 — "plain": the group label in normal text (not uppercase/mono). */
55
+ variant?: "default" | "plain";
56
+ /** F092.20 — sub-headed lists inside this group, shown after `items`. */
57
+ sections?: NavSection<Icon>[];
23
58
  }
24
59
  /**
25
60
  * Is this item the current page? Exact match for "/", otherwise the path is the
@@ -236,6 +271,10 @@ declare const TEXT: {
236
271
  readonly openMenu: "Åbn menu";
237
272
  readonly closeMenu: "Luk menu";
238
273
  readonly home: "Forside";
274
+ readonly working: "Arbejder";
275
+ readonly moreFor: "Mere for";
276
+ readonly back: "Tilbage";
277
+ readonly pinned: "Fastgjort";
239
278
  readonly language: "Sprog";
240
279
  readonly zoom: "Zoom";
241
280
  readonly zoomOut: "Zoom ud";
@@ -307,6 +346,10 @@ declare const TEXT: {
307
346
  readonly openMenu: "Open menu";
308
347
  readonly closeMenu: "Close menu";
309
348
  readonly home: "Home";
349
+ readonly working: "Working";
350
+ readonly moreFor: "More for";
351
+ readonly back: "Back";
352
+ readonly pinned: "Pinned";
310
353
  readonly language: "Language";
311
354
  readonly zoom: "Zoom";
312
355
  readonly zoomOut: "Zoom out";
@@ -420,6 +463,8 @@ interface SidebarProps {
420
463
  lang: Lang;
421
464
  /** F092.15 — rendered at the very bottom of the sidebar (the user menu in "sidebar-footer"). */
422
465
  bottom?: ComponentChildren;
466
+ /** F092.21 — a row action was chosen (value: the saved text of an `input` action). */
467
+ onItemAction?: (navItemId: string, actionId: string, value?: string) => void;
423
468
  }
424
469
  declare function Sidebar(p: SidebarProps): JSX.Element;
425
470
  interface NotificationBellProps {
@@ -552,6 +597,8 @@ declare function PageTabs(p: {
552
597
  onNavigate?: Navigate;
553
598
  }): JSX.Element;
554
599
  interface AppShellProps {
600
+ /** F092.21 — passed to the sidebar: a row action was chosen. */
601
+ onItemAction?: SidebarProps["onItemAction"];
555
602
  brand?: ComponentChildren;
556
603
  actions?: ComponentChildren;
557
604
  groups: PreactNavGroup[];
@@ -689,4 +736,4 @@ interface ConfirmDialogProps {
689
736
  }
690
737
  declare function ConfirmDialog(p: ConfirmDialogProps): JSX.Element | null;
691
738
 
692
- export { AVATAR_MAX_BYTES, AVATAR_TYPES, type AccountAdapter, AccountError, AccountPage, type AccountPageProps, type AccountProfile, type AnalyticsAdapter, type AnalyticsData, type AnalyticsDay, AnalyticsError, AnalyticsPage, type AnalyticsPageProps, AppShell, type AppShellProps, Avatar, ConfirmDialog, type ConfirmDialogProps, type Lang, type NavGroup, type NavItem, type Navigate, NotificationBell, type NotificationBellProps, type NotificationSource, PageHeader, type PageTab, PageTabs, type PreactNavGroup, type PreactNavItem, type ShellUser, Sidebar, type SidebarProps, TEXT, type TenantAdapter, TenantLoadError, type TenantMembershipRow, TenantSwitcher, type TenantSwitcherProps, TopBar, type TopBarProps, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuSection, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
739
+ export { AVATAR_MAX_BYTES, AVATAR_TYPES, type AccountAdapter, AccountError, AccountPage, type AccountPageProps, type AccountProfile, type AnalyticsAdapter, type AnalyticsData, type AnalyticsDay, AnalyticsError, AnalyticsPage, type AnalyticsPageProps, AppShell, type AppShellProps, Avatar, ConfirmDialog, type ConfirmDialogProps, type Lang, type NavAction, type NavGroup, type NavItem, type NavSection, type Navigate, NotificationBell, type NotificationBellProps, type NotificationSource, PageHeader, type PageTab, PageTabs, type PreactNavGroup, type PreactNavItem, type ShellUser, Sidebar, type SidebarProps, TEXT, type TenantAdapter, TenantLoadError, type TenantMembershipRow, TenantSwitcher, type TenantSwitcherProps, TopBar, type TopBarProps, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuSection, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
package/dist/preact.d.ts CHANGED
@@ -16,10 +16,45 @@ interface NavItem<Icon = unknown> {
16
16
  * it is on the current path.
17
17
  */
18
18
  children?: NavItem<Icon>[];
19
+ /** F092.20 — a status marker before the label; "working" = a run in progress. */
20
+ marker?: "working";
21
+ /** F092.20 — shows a pin icon after the label. */
22
+ pinned?: boolean;
23
+ /** F092.21 — row actions: a «…» button opens a menu with these. */
24
+ actions?: NavAction[];
25
+ }
26
+ /**
27
+ * F092.21 — one row action. `submenu` switches the menu to a sub-list (with a
28
+ * back arrow); `input` switches it to a field + save (e.g. rename). A plain
29
+ * action, a submenu leaf and a saved input all call `onItemAction`.
30
+ */
31
+ interface NavAction {
32
+ id: string;
33
+ label: string;
34
+ icon?: unknown;
35
+ /** A single letter that picks this action while the menu is open (e.g. "P"). */
36
+ kbd?: string;
37
+ /** Shown in the danger colour (delete). */
38
+ danger?: boolean;
39
+ submenu?: NavAction[];
40
+ input?: {
41
+ value: string;
42
+ placeholder?: string;
43
+ saveLabel?: string;
44
+ };
45
+ }
46
+ interface NavSection<Icon = unknown> {
47
+ /** A sub-heading inside one group, e.g. "I dag" / "I går". */
48
+ label: string;
49
+ items: NavItem<Icon>[];
19
50
  }
20
51
  interface NavGroup<Icon = unknown> {
21
52
  label: string;
22
53
  items: NavItem<Icon>[];
54
+ /** F092.20 — "plain": the group label in normal text (not uppercase/mono). */
55
+ variant?: "default" | "plain";
56
+ /** F092.20 — sub-headed lists inside this group, shown after `items`. */
57
+ sections?: NavSection<Icon>[];
23
58
  }
24
59
  /**
25
60
  * Is this item the current page? Exact match for "/", otherwise the path is the
@@ -236,6 +271,10 @@ declare const TEXT: {
236
271
  readonly openMenu: "Åbn menu";
237
272
  readonly closeMenu: "Luk menu";
238
273
  readonly home: "Forside";
274
+ readonly working: "Arbejder";
275
+ readonly moreFor: "Mere for";
276
+ readonly back: "Tilbage";
277
+ readonly pinned: "Fastgjort";
239
278
  readonly language: "Sprog";
240
279
  readonly zoom: "Zoom";
241
280
  readonly zoomOut: "Zoom ud";
@@ -307,6 +346,10 @@ declare const TEXT: {
307
346
  readonly openMenu: "Open menu";
308
347
  readonly closeMenu: "Close menu";
309
348
  readonly home: "Home";
349
+ readonly working: "Working";
350
+ readonly moreFor: "More for";
351
+ readonly back: "Back";
352
+ readonly pinned: "Pinned";
310
353
  readonly language: "Language";
311
354
  readonly zoom: "Zoom";
312
355
  readonly zoomOut: "Zoom out";
@@ -420,6 +463,8 @@ interface SidebarProps {
420
463
  lang: Lang;
421
464
  /** F092.15 — rendered at the very bottom of the sidebar (the user menu in "sidebar-footer"). */
422
465
  bottom?: ComponentChildren;
466
+ /** F092.21 — a row action was chosen (value: the saved text of an `input` action). */
467
+ onItemAction?: (navItemId: string, actionId: string, value?: string) => void;
423
468
  }
424
469
  declare function Sidebar(p: SidebarProps): JSX.Element;
425
470
  interface NotificationBellProps {
@@ -552,6 +597,8 @@ declare function PageTabs(p: {
552
597
  onNavigate?: Navigate;
553
598
  }): JSX.Element;
554
599
  interface AppShellProps {
600
+ /** F092.21 — passed to the sidebar: a row action was chosen. */
601
+ onItemAction?: SidebarProps["onItemAction"];
555
602
  brand?: ComponentChildren;
556
603
  actions?: ComponentChildren;
557
604
  groups: PreactNavGroup[];
@@ -689,4 +736,4 @@ interface ConfirmDialogProps {
689
736
  }
690
737
  declare function ConfirmDialog(p: ConfirmDialogProps): JSX.Element | null;
691
738
 
692
- export { AVATAR_MAX_BYTES, AVATAR_TYPES, type AccountAdapter, AccountError, AccountPage, type AccountPageProps, type AccountProfile, type AnalyticsAdapter, type AnalyticsData, type AnalyticsDay, AnalyticsError, AnalyticsPage, type AnalyticsPageProps, AppShell, type AppShellProps, Avatar, ConfirmDialog, type ConfirmDialogProps, type Lang, type NavGroup, type NavItem, type Navigate, NotificationBell, type NotificationBellProps, type NotificationSource, PageHeader, type PageTab, PageTabs, type PreactNavGroup, type PreactNavItem, type ShellUser, Sidebar, type SidebarProps, TEXT, type TenantAdapter, TenantLoadError, type TenantMembershipRow, TenantSwitcher, type TenantSwitcherProps, TopBar, type TopBarProps, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuSection, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
739
+ export { AVATAR_MAX_BYTES, AVATAR_TYPES, type AccountAdapter, AccountError, AccountPage, type AccountPageProps, type AccountProfile, type AnalyticsAdapter, type AnalyticsData, type AnalyticsDay, AnalyticsError, AnalyticsPage, type AnalyticsPageProps, AppShell, type AppShellProps, Avatar, ConfirmDialog, type ConfirmDialogProps, type Lang, type NavAction, type NavGroup, type NavItem, type NavSection, type Navigate, NotificationBell, type NotificationBellProps, type NotificationSource, PageHeader, type PageTab, PageTabs, type PreactNavGroup, type PreactNavItem, type ShellUser, Sidebar, type SidebarProps, TEXT, type TenantAdapter, TenantLoadError, type TenantMembershipRow, TenantSwitcher, type TenantSwitcherProps, TopBar, type TopBarProps, UserMenu, type UserMenuItem, type UserMenuProps, type UserMenuSection, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
package/dist/preact.js CHANGED
@@ -304,6 +304,10 @@ var TEXT = {
304
304
  openMenu: "\xC5bn menu",
305
305
  closeMenu: "Luk menu",
306
306
  home: "Forside",
307
+ working: "Arbejder",
308
+ moreFor: "Mere for",
309
+ back: "Tilbage",
310
+ pinned: "Fastgjort",
307
311
  language: "Sprog",
308
312
  zoom: "Zoom",
309
313
  zoomOut: "Zoom ud",
@@ -375,6 +379,10 @@ var TEXT = {
375
379
  openMenu: "Open menu",
376
380
  closeMenu: "Close menu",
377
381
  home: "Home",
382
+ working: "Working",
383
+ moreFor: "More for",
384
+ back: "Back",
385
+ pinned: "Pinned",
378
386
  language: "Language",
379
387
  zoom: "Zoom",
380
388
  zoomOut: "Zoom out",
@@ -493,10 +501,194 @@ var IconLogOut = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
493
501
  /* @__PURE__ */ jsx("path", { d: "m16 17 5-5-5-5" }),
494
502
  /* @__PURE__ */ jsx("path", { d: "M21 12H9" })
495
503
  ] }), 14);
504
+ var IconPin = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
505
+ /* @__PURE__ */ jsx("path", { d: "M12 17v5" }),
506
+ /* @__PURE__ */ jsx("path", { d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" })
507
+ ] }), 12);
496
508
  var IconUser = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
497
509
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
498
510
  /* @__PURE__ */ jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })
499
511
  ] }), 14);
512
+ function RowActions(props) {
513
+ const t = TEXT[props.lang];
514
+ const id = props.item.id;
515
+ const btn = useRef(null);
516
+ const menu = useRef(null);
517
+ const [stack, setStack] = useState([]);
518
+ const [hi, setHi] = useState(0);
519
+ const [value, setValue] = useState("");
520
+ const [pos, setPos] = useState(void 0);
521
+ const view = stack[stack.length - 1];
522
+ const open = !!view;
523
+ const close = (refocus = true) => {
524
+ setStack([]);
525
+ if (refocus) btn.current?.focus();
526
+ };
527
+ const show = (v) => {
528
+ setStack((s) => [...s, v]);
529
+ setHi(0);
530
+ if (v.kind === "input") setValue(v.action.input.value);
531
+ };
532
+ const back = () => {
533
+ setStack((s) => s.slice(0, -1));
534
+ setHi(0);
535
+ };
536
+ const pick = (a) => {
537
+ if (a.submenu?.length) return show({ kind: "list", actions: a.submenu, title: a.label });
538
+ if (a.input) return show({ kind: "input", action: a });
539
+ close();
540
+ props.onAction(a.id);
541
+ };
542
+ const save = (a) => {
543
+ close();
544
+ props.onAction(a.id, value);
545
+ };
546
+ useLayoutEffect(() => {
547
+ if (!open || !btn.current) return setPos(void 0);
548
+ const r = btn.current.getBoundingClientRect();
549
+ const vw = window.innerWidth, vh = window.innerHeight, m = 8;
550
+ const w = Math.min(240, vw - 2 * m);
551
+ const h = menu.current?.offsetHeight ?? 0;
552
+ const left = Math.max(m, Math.min(r.right - w, vw - w - m));
553
+ const top = r.bottom + 4 + h <= vh - m || r.top - 4 - h < m ? Math.min(r.bottom + 4, vh - m - h) : r.top - 4 - h;
554
+ setPos({ position: "fixed", left: `${left}px`, top: `${Math.max(m, top)}px`, width: `${w}px` });
555
+ if (view?.kind === "input") menu.current?.querySelector("input")?.focus();
556
+ else menu.current?.focus();
557
+ }, [open, stack.length]);
558
+ useEffect(() => {
559
+ if (!open) return;
560
+ const outside = (e) => {
561
+ const n = e.target instanceof Node ? e.target : null;
562
+ if (n && (menu.current?.contains(n) || btn.current?.contains(n))) return;
563
+ close(false);
564
+ };
565
+ document.addEventListener("mousedown", outside);
566
+ window.addEventListener("scroll", outside, true);
567
+ window.addEventListener("resize", outside);
568
+ return () => {
569
+ document.removeEventListener("mousedown", outside);
570
+ window.removeEventListener("scroll", outside, true);
571
+ window.removeEventListener("resize", outside);
572
+ };
573
+ }, [open]);
574
+ const onKeyDown = (e) => {
575
+ if (!view) return;
576
+ e.stopPropagation();
577
+ if (e.key === "Escape") {
578
+ e.preventDefault();
579
+ return close();
580
+ }
581
+ if (view.kind === "input") {
582
+ if (e.key === "Enter") {
583
+ e.preventDefault();
584
+ save(view.action);
585
+ }
586
+ return;
587
+ }
588
+ if ((e.key === "ArrowLeft" || e.key === "Backspace") && stack.length > 1) {
589
+ e.preventDefault();
590
+ return back();
591
+ }
592
+ const list = view.actions;
593
+ const { state, intent } = selectKeyReducer({ open: true, highlighted: hi }, e.key, list.length);
594
+ if (intent.type === "select") {
595
+ e.preventDefault();
596
+ return pick(list[intent.index]);
597
+ }
598
+ if (state.highlighted !== hi) {
599
+ e.preventDefault();
600
+ return setHi(state.highlighted);
601
+ }
602
+ const byKbd = e.key.length === 1 ? list.find((a) => a.kbd?.toLowerCase() === e.key.toLowerCase()) : void 0;
603
+ if (byKbd) {
604
+ e.preventDefault();
605
+ pick(byKbd);
606
+ }
607
+ };
608
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
609
+ /* @__PURE__ */ jsx(
610
+ "button",
611
+ {
612
+ ref: btn,
613
+ type: "button",
614
+ class: "bas-item__more" + (open ? " is-open" : ""),
615
+ "data-testid": `sidebar-item-${id}-more`,
616
+ "aria-label": `${t.moreFor} ${props.item.label}`,
617
+ "aria-haspopup": "menu",
618
+ "aria-expanded": open,
619
+ onClick: () => open ? close() : show({ kind: "list", actions: props.item.actions }),
620
+ children: /* @__PURE__ */ jsx(IconMore, {})
621
+ }
622
+ ),
623
+ view ? /* @__PURE__ */ jsxs(
624
+ "div",
625
+ {
626
+ ref: menu,
627
+ class: "bas-rowmenu",
628
+ role: "menu",
629
+ tabIndex: -1,
630
+ "aria-label": view.kind === "list" && view.title ? view.title : props.item.label,
631
+ "data-testid": `sidebar-item-${id}-menu`,
632
+ style: pos ?? { position: "fixed", visibility: "hidden" },
633
+ onKeyDown,
634
+ children: [
635
+ stack.length > 1 ? /* @__PURE__ */ jsxs("button", { type: "button", class: "bas-rowmenu__back", "data-testid": `sidebar-item-${id}-back`, "aria-label": t.back, onClick: back, children: [
636
+ /* @__PURE__ */ jsx(IconChevronLeft, {}),
637
+ /* @__PURE__ */ jsx("span", { children: view.kind === "list" ? view.title : view.action.label })
638
+ ] }) : null,
639
+ view.kind === "list" ? view.actions.map((a, i) => /* @__PURE__ */ jsxs(
640
+ "button",
641
+ {
642
+ type: "button",
643
+ role: "menuitem",
644
+ class: "bas-rowmenu__item" + (i === hi ? " is-active" : "") + (a.danger ? " is-danger" : ""),
645
+ "data-testid": `sidebar-item-${id}-action-${a.id}`,
646
+ onMouseEnter: () => setHi(i),
647
+ onClick: () => pick(a),
648
+ children: [
649
+ a.icon ? /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__icon", children: a.icon }) : null,
650
+ /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__label", children: a.label }),
651
+ a.submenu?.length ? /* @__PURE__ */ jsx(IconChevronRight, {}) : a.kbd ? /* @__PURE__ */ jsx("kbd", { class: "bas-rowmenu__kbd", children: a.kbd.toUpperCase() }) : null
652
+ ]
653
+ },
654
+ a.id
655
+ )) : /* @__PURE__ */ jsxs(
656
+ "form",
657
+ {
658
+ class: "bas-rowmenu__form",
659
+ "data-testid": `sidebar-item-${id}-form`,
660
+ onSubmit: (e) => {
661
+ e.preventDefault();
662
+ save(view.action);
663
+ },
664
+ children: [
665
+ /* @__PURE__ */ jsx(
666
+ "input",
667
+ {
668
+ class: "bas-rowmenu__input",
669
+ "data-testid": `sidebar-item-${id}-input`,
670
+ "aria-label": view.action.label,
671
+ value,
672
+ placeholder: view.action.input.placeholder,
673
+ onInput: (e) => setValue(e.currentTarget.value)
674
+ }
675
+ ),
676
+ /* @__PURE__ */ jsx("button", { type: "submit", class: "bas-rowmenu__save", "data-testid": `sidebar-item-${id}-save`, children: view.action.input.saveLabel ?? t.save })
677
+ ]
678
+ }
679
+ )
680
+ ]
681
+ }
682
+ ) : null
683
+ ] });
684
+ }
685
+ var IconMore = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
686
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
687
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1" }),
688
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1" })
689
+ ] }), 16);
690
+ var IconChevronLeft = () => svg(/* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }), 14);
691
+ var IconChevronRight = () => svg(/* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }), 14);
500
692
  function Sidebar(p) {
501
693
  const key = p.groupsKey ?? "broberg-app-shell.groups";
502
694
  const itemsKey = p.itemsKey ?? "broberg-app-shell.items";
@@ -554,7 +746,7 @@ function Sidebar(p) {
554
746
  const leaf = (it, sub = false) => {
555
747
  const active = isActivePath(it.href, p.currentPath);
556
748
  const badge = badgeLabel(it.badge);
557
- return /* @__PURE__ */ jsxs(
749
+ const link = /* @__PURE__ */ jsxs(
558
750
  NavLink,
559
751
  {
560
752
  href: it.href,
@@ -568,12 +760,19 @@ function Sidebar(p) {
568
760
  children: [
569
761
  active && !sub ? /* @__PURE__ */ jsx("span", { class: "bas-item__bar" }) : null,
570
762
  it.icon ? /* @__PURE__ */ jsx("span", { class: "bas-item__icon", children: it.icon }) : null,
763
+ it.marker === "working" ? /* @__PURE__ */ jsx("span", { class: "bas-item__marker", "data-testid": `sidebar-item-${it.id}-marker`, role: "img", "aria-label": TEXT[p.lang].working }) : null,
571
764
  /* @__PURE__ */ jsx("span", { class: "bas-item__label", children: it.label }),
765
+ it.pinned ? /* @__PURE__ */ jsx("span", { class: "bas-item__pin", "data-testid": `sidebar-item-${it.id}-pinned`, role: "img", "aria-label": TEXT[p.lang].pinned, children: /* @__PURE__ */ jsx(IconPin, {}) }) : null,
572
766
  badge ? /* @__PURE__ */ jsx("span", { class: "bas-item__count", "data-testid": `sidebar-badge-${it.id}`, children: badge }) : null
573
767
  ]
574
768
  },
575
769
  it.id
576
770
  );
771
+ if (!it.actions?.length || railed) return link;
772
+ return /* @__PURE__ */ jsxs("div", { class: "bas-itemrow", children: [
773
+ link,
774
+ /* @__PURE__ */ jsx(RowActions, { item: it, lang: p.lang, onAction: (a, v) => v === void 0 ? p.onItemAction?.(it.id, a) : p.onItemAction?.(it.id, a, v) })
775
+ ] }, it.id);
577
776
  };
578
777
  const parent = (it) => {
579
778
  const children = it.children;
@@ -654,12 +853,16 @@ function Sidebar(p) {
654
853
  /* @__PURE__ */ jsx("nav", { class: "bas-sidebar__nav", children: p.groups.map((g) => {
655
854
  const open = railed || !folded.has(g.label);
656
855
  const slug = g.label.toLowerCase().replace(/[^a-z0-9]+/g, "-");
657
- return /* @__PURE__ */ jsxs("div", { class: "bas-group", children: [
856
+ return /* @__PURE__ */ jsxs("div", { class: "bas-group" + (g.variant === "plain" ? " bas-group--plain" : ""), children: [
658
857
  /* @__PURE__ */ jsxs("button", { type: "button", class: "bas-group__label", "data-testid": `sidebar-group-${slug}`, "aria-expanded": open, onClick: () => toggleGroup(g.label), children: [
659
858
  /* @__PURE__ */ jsx(IconChevron, { open }),
660
859
  /* @__PURE__ */ jsx("span", { children: g.label })
661
860
  ] }),
662
- open ? g.items.map(item) : null
861
+ open ? g.items.map(item) : null,
862
+ open ? g.sections?.map((sec) => /* @__PURE__ */ jsxs("div", { class: "bas-section", "data-testid": `sidebar-section-${slug}-${sec.label.toLowerCase().replace(/[^a-z0-9æøå]+/g, "-")}`, children: [
863
+ /* @__PURE__ */ jsx("div", { class: "bas-section__label", children: sec.label }),
864
+ sec.items.map(item)
865
+ ] }, sec.label)) : null
663
866
  ] }, g.label);
664
867
  }) }),
665
868
  p.footer?.length ? /* @__PURE__ */ jsx("div", { class: "bas-sidebar__foot", children: p.footer.map(item) }) : null,
@@ -1241,6 +1444,7 @@ function AppShell(p) {
1241
1444
  {
1242
1445
  groups: p.groups,
1243
1446
  footer: p.footer,
1447
+ onItemAction: p.onItemAction,
1244
1448
  currentPath: p.currentPath,
1245
1449
  onNavigate: p.onNavigate,
1246
1450
  collapsed,