@broberg/app-shell 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/preact.d.cts CHANGED
@@ -73,6 +73,18 @@ interface NavGroup<Icon = unknown> {
73
73
  * group is always open, even with a stored fold for this label. Default true.
74
74
  */
75
75
  collapsible?: boolean;
76
+ /**
77
+ * F092.28 — a small icon button at the right of the heading row (e.g. «Filtrér og
78
+ * sortér»). The app opens its own menu from `onClick(anchor)`. Clicking it never
79
+ * folds the group. Not shown with `hideLabel` (there is no heading row).
80
+ */
81
+ action?: {
82
+ icon: Icon;
83
+ label: string;
84
+ expanded?: boolean;
85
+ onClick: (anchor: HTMLElement) => void;
86
+ testid?: string;
87
+ };
76
88
  }
77
89
  /**
78
90
  * Is this item the current page? Exact match for "/", otherwise the path is the
@@ -447,6 +459,34 @@ type PreactNavGroup = NavGroup<Icon>;
447
459
  type Navigate = (href: string) => void;
448
460
  /** True below 768px. Follows the window as it resizes. */
449
461
  declare function useIsMobile(): boolean;
462
+ interface ItemActionsMenuProps {
463
+ /** The menu's items — the same NavAction[] a sidebar row takes (kbd, submenu, input, confirm, danger). */
464
+ actions: NavAction[];
465
+ /** What the menu opens from: positioned from it, and focus returns to it on close. */
466
+ anchor: HTMLElement;
467
+ /** An action was chosen (value = the text, for an input action). The menu has closed. */
468
+ onAction: (actionId: string, value?: string) => void;
469
+ /** The menu closed — by a pick, Escape, a click outside, scroll or resize. Unmount it here. */
470
+ onClose: () => void;
471
+ lang: Lang;
472
+ /** The menu's accessible name, e.g. the conversation's title. */
473
+ label: string;
474
+ /** "auto" (default): below the anchor if it fits, else above. "down": always below. */
475
+ placement?: "auto" | "down";
476
+ /** testids: `<prefix>-menu`, `<prefix>-action-<id>`, `<prefix>-back`, `<prefix>-input`, `<prefix>-save`. Default "item-actions". */
477
+ testidPrefix?: string;
478
+ }
479
+ /**
480
+ * F092.29 — the row-action menu on its own, so a title «⌄» and a sidebar row «…»
481
+ * open THE SAME menu (scout #2745). The sidebar's row menu is this component: one
482
+ * implementation, so keyboard, confirm, input and submenu cannot drift apart.
483
+ * Mount it while open; it closes itself through onClose.
484
+ *
485
+ * Keys (F092.21/.22): focus moves into the menu when it opens; arrows move real
486
+ * focus; Enter/Space act on the FOCUSED item; a kbd letter picks; ←/Backspace goes
487
+ * back from a submenu; Escape closes and returns focus to the anchor.
488
+ */
489
+ declare function ItemActionsMenu(props: ItemActionsMenuProps): JSX.Element;
450
490
  interface SidebarProps {
451
491
  groups: PreactNavGroup[];
452
492
  footer?: PreactNavItem[];
@@ -500,6 +540,8 @@ interface ShellUser {
500
540
  picture?: string;
501
541
  /** F092.15 — a line under the name, e.g. «Administrator · Enhed Nord». */
502
542
  subtitle?: string;
543
+ /** F092.27 — the organisation, e.g. «broberg.ai». Shown ONLY in the menu's top, with a building icon. */
544
+ org?: string;
503
545
  }
504
546
  interface UserMenuItem {
505
547
  id: string;
@@ -760,4 +802,4 @@ interface ConfirmDialogProps {
760
802
  }
761
803
  declare function ConfirmDialog(p: ConfirmDialogProps): JSX.Element | null;
762
804
 
763
- 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 };
805
+ 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, ItemActionsMenu, type ItemActionsMenuProps, 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
@@ -73,6 +73,18 @@ interface NavGroup<Icon = unknown> {
73
73
  * group is always open, even with a stored fold for this label. Default true.
74
74
  */
75
75
  collapsible?: boolean;
76
+ /**
77
+ * F092.28 — a small icon button at the right of the heading row (e.g. «Filtrér og
78
+ * sortér»). The app opens its own menu from `onClick(anchor)`. Clicking it never
79
+ * folds the group. Not shown with `hideLabel` (there is no heading row).
80
+ */
81
+ action?: {
82
+ icon: Icon;
83
+ label: string;
84
+ expanded?: boolean;
85
+ onClick: (anchor: HTMLElement) => void;
86
+ testid?: string;
87
+ };
76
88
  }
77
89
  /**
78
90
  * Is this item the current page? Exact match for "/", otherwise the path is the
@@ -447,6 +459,34 @@ type PreactNavGroup = NavGroup<Icon>;
447
459
  type Navigate = (href: string) => void;
448
460
  /** True below 768px. Follows the window as it resizes. */
449
461
  declare function useIsMobile(): boolean;
462
+ interface ItemActionsMenuProps {
463
+ /** The menu's items — the same NavAction[] a sidebar row takes (kbd, submenu, input, confirm, danger). */
464
+ actions: NavAction[];
465
+ /** What the menu opens from: positioned from it, and focus returns to it on close. */
466
+ anchor: HTMLElement;
467
+ /** An action was chosen (value = the text, for an input action). The menu has closed. */
468
+ onAction: (actionId: string, value?: string) => void;
469
+ /** The menu closed — by a pick, Escape, a click outside, scroll or resize. Unmount it here. */
470
+ onClose: () => void;
471
+ lang: Lang;
472
+ /** The menu's accessible name, e.g. the conversation's title. */
473
+ label: string;
474
+ /** "auto" (default): below the anchor if it fits, else above. "down": always below. */
475
+ placement?: "auto" | "down";
476
+ /** testids: `<prefix>-menu`, `<prefix>-action-<id>`, `<prefix>-back`, `<prefix>-input`, `<prefix>-save`. Default "item-actions". */
477
+ testidPrefix?: string;
478
+ }
479
+ /**
480
+ * F092.29 — the row-action menu on its own, so a title «⌄» and a sidebar row «…»
481
+ * open THE SAME menu (scout #2745). The sidebar's row menu is this component: one
482
+ * implementation, so keyboard, confirm, input and submenu cannot drift apart.
483
+ * Mount it while open; it closes itself through onClose.
484
+ *
485
+ * Keys (F092.21/.22): focus moves into the menu when it opens; arrows move real
486
+ * focus; Enter/Space act on the FOCUSED item; a kbd letter picks; ←/Backspace goes
487
+ * back from a submenu; Escape closes and returns focus to the anchor.
488
+ */
489
+ declare function ItemActionsMenu(props: ItemActionsMenuProps): JSX.Element;
450
490
  interface SidebarProps {
451
491
  groups: PreactNavGroup[];
452
492
  footer?: PreactNavItem[];
@@ -500,6 +540,8 @@ interface ShellUser {
500
540
  picture?: string;
501
541
  /** F092.15 — a line under the name, e.g. «Administrator · Enhed Nord». */
502
542
  subtitle?: string;
543
+ /** F092.27 — the organisation, e.g. «broberg.ai». Shown ONLY in the menu's top, with a building icon. */
544
+ org?: string;
503
545
  }
504
546
  interface UserMenuItem {
505
547
  id: string;
@@ -760,4 +802,4 @@ interface ConfirmDialogProps {
760
802
  }
761
803
  declare function ConfirmDialog(p: ConfirmDialogProps): JSX.Element | null;
762
804
 
763
- 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 };
805
+ 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, ItemActionsMenu, type ItemActionsMenuProps, 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
@@ -1,8 +1,8 @@
1
- import { useState, useEffect, useRef, useMemo, useLayoutEffect } from 'preact/hooks';
1
+ import { useState, useEffect, useRef, useLayoutEffect, useMemo } from 'preact/hooks';
2
2
  import { createBellShell } from '@broberg/notifications/shell';
3
3
  import { selectKeyReducer } from '@broberg/ui-controls-core';
4
4
  import { getPreference, getPalette, getSurfaces, getBackdrop, onThemeChange, onPaletteChange, onSurfacesChange, onBackdropChange, setPreference, setPalette, PALETTES, PALETTE_LABELS, setSurfaces, setBackdrop } from '@broberg/theme';
5
- import { jsxs, Fragment, jsx } from 'preact/jsx-runtime';
5
+ import { jsxs, jsx, Fragment } from 'preact/jsx-runtime';
6
6
 
7
7
  // src/preact.tsx
8
8
 
@@ -509,12 +509,22 @@ var IconUser = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
509
509
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
510
510
  /* @__PURE__ */ jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })
511
511
  ] }), 14);
512
- function RowActions(props) {
512
+ var IconBuilding = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [
513
+ /* @__PURE__ */ jsx("rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }),
514
+ /* @__PURE__ */ jsx("path", { d: "M9 22v-4h6v4" }),
515
+ /* @__PURE__ */ jsx("path", { d: "M8 6h.01M16 6h.01M12 6h.01M12 10h.01M12 14h.01M16 10h.01M16 14h.01M8 10h.01M8 14h.01" })
516
+ ] }), 15);
517
+ function ItemActionsMenu(props) {
513
518
  const t = TEXT[props.lang];
514
- const id = props.item.id;
515
- const btn = useRef(null);
519
+ const pre = props.testidPrefix ?? "item-actions";
516
520
  const menu = useRef(null);
517
- const [stack, setStack] = useState([]);
521
+ const first = [{ kind: "list", actions: props.actions }];
522
+ const [stack, setStack] = useState(first);
523
+ const stackRef = useRef(first);
524
+ const setStackNow = (next) => {
525
+ stackRef.current = next;
526
+ setStack(next);
527
+ };
518
528
  const [hi, setHiState] = useState(0);
519
529
  const hiRef = useRef(0);
520
530
  const setHi = (n) => {
@@ -533,16 +543,12 @@ function RowActions(props) {
533
543
  const [value, setValue] = useState("");
534
544
  const [pos, setPos] = useState(void 0);
535
545
  const view = stack[stack.length - 1];
536
- const open = !!view;
537
- const stackRef = useRef([]);
538
- const setStackNow = (next) => {
539
- stackRef.current = next;
540
- setStack(next);
541
- };
546
+ const closed = useRef(false);
542
547
  const close = (refocus = true) => {
543
- setStackNow([]);
544
- setArmed(null);
545
- if (refocus) btn.current?.focus();
548
+ if (closed.current) return;
549
+ closed.current = true;
550
+ if (refocus) props.anchor.focus();
551
+ props.onClose();
546
552
  };
547
553
  const show = (v) => {
548
554
  setStackNow([...stackRef.current, v]);
@@ -567,25 +573,24 @@ function RowActions(props) {
567
573
  props.onAction(a.id, value);
568
574
  };
569
575
  useLayoutEffect(() => {
570
- if (!open || !btn.current) return setPos(void 0);
571
- const r = btn.current.getBoundingClientRect();
576
+ const r = props.anchor.getBoundingClientRect();
572
577
  const vw = window.innerWidth, vh = window.innerHeight, m = 8;
573
578
  const w = Math.min(240, vw - 2 * m);
574
579
  const h = menu.current?.offsetHeight ?? 0;
575
580
  const left = Math.max(m, Math.min(r.right - w, vw - w - m));
576
- 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;
581
+ const fitsBelow = r.bottom + 4 + h <= vh - m;
582
+ const top = props.placement === "down" || fitsBelow || r.top - 4 - h < m ? Math.min(r.bottom + 4, vh - m - h) : r.top - 4 - h;
577
583
  setPos({ position: "fixed", left: `${left}px`, top: `${Math.max(m, top)}px`, width: `${w}px` });
578
- }, [open, stack.length]);
584
+ }, [stack.length]);
579
585
  useLayoutEffect(() => {
580
- if (!open || !pos) return;
581
- if (view?.kind === "input") menu.current?.querySelector("input")?.focus();
586
+ if (!pos) return;
587
+ if (view.kind === "input") menu.current?.querySelector("input")?.focus();
582
588
  else items.current[hiRef.current]?.focus();
583
- }, [open, pos, hi, stack.length]);
589
+ }, [pos, hi, stack.length]);
584
590
  useLayoutEffect(() => {
585
- if (!open) return;
586
591
  const outside = (e) => {
587
592
  const n = e.target instanceof Node ? e.target : null;
588
- if (n && (menu.current?.contains(n) || btn.current?.contains(n))) return;
593
+ if (n && (menu.current?.contains(n) || props.anchor.contains(n))) return;
589
594
  close(false);
590
595
  };
591
596
  const early = (e) => {
@@ -604,8 +609,9 @@ function RowActions(props) {
604
609
  window.removeEventListener("scroll", outside, true);
605
610
  window.removeEventListener("resize", outside);
606
611
  };
607
- }, [open]);
612
+ }, []);
608
613
  const onKeyDown = (e) => {
614
+ if (closed.current) return;
609
615
  const stack2 = stackRef.current;
610
616
  const view2 = stack2[stack2.length - 1];
611
617
  if (!view2) return;
@@ -648,6 +654,82 @@ function RowActions(props) {
648
654
  }
649
655
  };
650
656
  onKeyDownRef.current = onKeyDown;
657
+ return /* @__PURE__ */ jsxs(
658
+ "div",
659
+ {
660
+ ref: menu,
661
+ class: "bas-rowmenu",
662
+ role: "menu",
663
+ tabIndex: -1,
664
+ "aria-label": view.kind === "list" && view.title ? view.title : props.label,
665
+ "data-testid": `${pre}-menu`,
666
+ style: pos ?? { position: "fixed", visibility: "hidden" },
667
+ onKeyDown,
668
+ children: [
669
+ stack.length > 1 ? /* @__PURE__ */ jsxs("button", { type: "button", class: "bas-rowmenu__back", "data-testid": `${pre}-back`, "aria-label": t.back, onClick: back, children: [
670
+ /* @__PURE__ */ jsx(IconChevronLeft, {}),
671
+ /* @__PURE__ */ jsx("span", { children: view.kind === "list" ? view.title : view.action.label })
672
+ ] }) : null,
673
+ view.kind === "list" ? view.actions.map((a, i) => /* @__PURE__ */ jsxs(
674
+ "button",
675
+ {
676
+ type: "button",
677
+ role: "menuitem",
678
+ ref: (el) => {
679
+ items.current[i] = el;
680
+ },
681
+ tabIndex: i === hi ? 0 : -1,
682
+ class: "bas-rowmenu__item" + (i === hi ? " is-active" : "") + (a.danger ? " is-danger" : "") + (armed === a.id ? " is-armed" : ""),
683
+ "data-testid": `${pre}-action-${a.id}`,
684
+ onMouseEnter: () => setHi(i),
685
+ onFocus: () => setHi(i),
686
+ onClick: () => pick(a),
687
+ children: [
688
+ a.icon ? /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__icon", children: a.icon }) : null,
689
+ /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__label", children: armed === a.id && a.confirm ? a.confirm.label : a.label }),
690
+ a.submenu?.length ? /* @__PURE__ */ jsx(IconChevronRight, {}) : a.kbd ? /* @__PURE__ */ jsx("kbd", { class: "bas-rowmenu__kbd", children: a.kbd.toUpperCase() }) : null
691
+ ]
692
+ },
693
+ a.id
694
+ )) : /* @__PURE__ */ jsxs(
695
+ "form",
696
+ {
697
+ class: "bas-rowmenu__form",
698
+ "data-testid": `${pre}-form`,
699
+ onSubmit: (e) => {
700
+ e.preventDefault();
701
+ save(view.action);
702
+ },
703
+ children: [
704
+ /* @__PURE__ */ jsx(
705
+ "input",
706
+ {
707
+ class: "bas-rowmenu__input",
708
+ "data-testid": `${pre}-input`,
709
+ "aria-label": view.action.label,
710
+ value,
711
+ placeholder: view.action.input.placeholder,
712
+ onInput: (e) => setValue(e.currentTarget.value)
713
+ }
714
+ ),
715
+ /* @__PURE__ */ jsx("button", { type: "submit", class: "bas-rowmenu__save", "data-testid": `${pre}-save`, children: view.action.input.saveLabel ?? t.save })
716
+ ]
717
+ }
718
+ )
719
+ ]
720
+ }
721
+ );
722
+ }
723
+ function RowActions(props) {
724
+ const t = TEXT[props.lang];
725
+ const id = props.item.id;
726
+ const btn = useRef(null);
727
+ const [open, setOpenState] = useState(false);
728
+ const openRef = useRef(false);
729
+ const setOpen = (v) => {
730
+ openRef.current = v;
731
+ setOpenState(v);
732
+ };
651
733
  return /* @__PURE__ */ jsxs(Fragment, { children: [
652
734
  /* @__PURE__ */ jsx(
653
735
  "button",
@@ -659,73 +741,24 @@ function RowActions(props) {
659
741
  "aria-label": `${t.moreFor} ${props.item.label}`,
660
742
  "aria-haspopup": "menu",
661
743
  "aria-expanded": open,
662
- onClick: () => stackRef.current.length ? close() : show({ kind: "list", actions: props.item.actions }),
744
+ onClick: () => {
745
+ if (!openRef.current) return setOpen(true);
746
+ setOpen(false);
747
+ btn.current?.focus();
748
+ },
663
749
  children: /* @__PURE__ */ jsx(IconMore, {})
664
750
  }
665
751
  ),
666
- view ? /* @__PURE__ */ jsxs(
667
- "div",
752
+ open && btn.current ? /* @__PURE__ */ jsx(
753
+ ItemActionsMenu,
668
754
  {
669
- ref: menu,
670
- class: "bas-rowmenu",
671
- role: "menu",
672
- tabIndex: -1,
673
- "aria-label": view.kind === "list" && view.title ? view.title : props.item.label,
674
- "data-testid": `sidebar-item-${id}-menu`,
675
- style: pos ?? { position: "fixed", visibility: "hidden" },
676
- onKeyDown,
677
- children: [
678
- 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: [
679
- /* @__PURE__ */ jsx(IconChevronLeft, {}),
680
- /* @__PURE__ */ jsx("span", { children: view.kind === "list" ? view.title : view.action.label })
681
- ] }) : null,
682
- view.kind === "list" ? view.actions.map((a, i) => /* @__PURE__ */ jsxs(
683
- "button",
684
- {
685
- type: "button",
686
- role: "menuitem",
687
- ref: (el) => {
688
- items.current[i] = el;
689
- },
690
- tabIndex: i === hi ? 0 : -1,
691
- class: "bas-rowmenu__item" + (i === hi ? " is-active" : "") + (a.danger ? " is-danger" : "") + (armed === a.id ? " is-armed" : ""),
692
- "data-testid": `sidebar-item-${id}-action-${a.id}`,
693
- onMouseEnter: () => setHi(i),
694
- onFocus: () => setHi(i),
695
- onClick: () => pick(a),
696
- children: [
697
- a.icon ? /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__icon", children: a.icon }) : null,
698
- /* @__PURE__ */ jsx("span", { class: "bas-rowmenu__label", children: armed === a.id && a.confirm ? a.confirm.label : a.label }),
699
- a.submenu?.length ? /* @__PURE__ */ jsx(IconChevronRight, {}) : a.kbd ? /* @__PURE__ */ jsx("kbd", { class: "bas-rowmenu__kbd", children: a.kbd.toUpperCase() }) : null
700
- ]
701
- },
702
- a.id
703
- )) : /* @__PURE__ */ jsxs(
704
- "form",
705
- {
706
- class: "bas-rowmenu__form",
707
- "data-testid": `sidebar-item-${id}-form`,
708
- onSubmit: (e) => {
709
- e.preventDefault();
710
- save(view.action);
711
- },
712
- children: [
713
- /* @__PURE__ */ jsx(
714
- "input",
715
- {
716
- class: "bas-rowmenu__input",
717
- "data-testid": `sidebar-item-${id}-input`,
718
- "aria-label": view.action.label,
719
- value,
720
- placeholder: view.action.input.placeholder,
721
- onInput: (e) => setValue(e.currentTarget.value)
722
- }
723
- ),
724
- /* @__PURE__ */ jsx("button", { type: "submit", class: "bas-rowmenu__save", "data-testid": `sidebar-item-${id}-save`, children: view.action.input.saveLabel ?? t.save })
725
- ]
726
- }
727
- )
728
- ]
755
+ actions: props.item.actions,
756
+ anchor: btn.current,
757
+ lang: props.lang,
758
+ label: props.item.label,
759
+ testidPrefix: `sidebar-item-${id}`,
760
+ onAction: props.onAction,
761
+ onClose: () => setOpen(false)
729
762
  }
730
763
  ) : null
731
764
  ] });
@@ -910,10 +943,32 @@ function Sidebar(p) {
910
943
  "aria-label": g.hideLabel ? g.label : void 0,
911
944
  "data-testid": g.hideLabel ? `sidebar-group-${slug}-unlabelled` : void 0,
912
945
  children: [
913
- g.hideLabel ? null : g.collapsible === false ? /* @__PURE__ */ jsx("div", { class: "bas-group__label bas-group__label--static", "data-testid": `sidebar-group-${slug}`, children: /* @__PURE__ */ jsx("span", { children: g.label }) }) : /* @__PURE__ */ jsxs("button", { type: "button", class: "bas-group__label", "data-testid": `sidebar-group-${slug}`, "aria-expanded": open, onClick: () => toggleGroup(g.label), children: [
914
- /* @__PURE__ */ jsx(IconChevron, { open }),
915
- /* @__PURE__ */ jsx("span", { children: g.label })
916
- ] }),
946
+ (() => {
947
+ if (g.hideLabel) return null;
948
+ const heading = g.collapsible === false ? /* @__PURE__ */ jsx("div", { class: "bas-group__label bas-group__label--static", "data-testid": `sidebar-group-${slug}`, children: /* @__PURE__ */ jsx("span", { children: g.label }) }) : /* @__PURE__ */ jsxs("button", { type: "button", class: "bas-group__label", "data-testid": `sidebar-group-${slug}`, "aria-expanded": open, onClick: () => toggleGroup(g.label), children: [
949
+ /* @__PURE__ */ jsx(IconChevron, { open }),
950
+ /* @__PURE__ */ jsx("span", { children: g.label })
951
+ ] });
952
+ if (!g.action) return heading;
953
+ const a = g.action;
954
+ return /* @__PURE__ */ jsxs("div", { class: "bas-group__head", children: [
955
+ heading,
956
+ /* @__PURE__ */ jsx(
957
+ "button",
958
+ {
959
+ type: "button",
960
+ class: "bas-group__action",
961
+ "data-testid": a.testid ?? `sidebar-group-${slug}-action`,
962
+ "aria-label": a.label,
963
+ title: a.label,
964
+ "aria-haspopup": "menu",
965
+ "aria-expanded": a.expanded ?? false,
966
+ onClick: (e) => a.onClick(e.currentTarget),
967
+ children: a.icon
968
+ }
969
+ )
970
+ ] });
971
+ })(),
917
972
  open ? g.items.map(item) : null,
918
973
  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: [
919
974
  /* @__PURE__ */ jsx("div", { class: "bas-section__label", children: sec.label }),
@@ -1273,7 +1328,11 @@ function UserMenu(p) {
1273
1328
  /* @__PURE__ */ jsxs("div", { class: "bas-menu__whotext", children: [
1274
1329
  p.user.name ? /* @__PURE__ */ jsx("div", { class: "bas-menu__name", children: p.user.name }) : null,
1275
1330
  p.user.subtitle ? /* @__PURE__ */ jsx("div", { class: "bas-menu__sub", children: p.user.subtitle }) : null,
1276
- p.user.email ? /* @__PURE__ */ jsx("div", { class: "bas-menu__email", children: p.user.email }) : null
1331
+ p.user.email ? /* @__PURE__ */ jsx("div", { class: "bas-menu__email", children: p.user.email }) : null,
1332
+ p.user.org ? /* @__PURE__ */ jsxs("div", { class: "bas-menu__org", "data-testid": "user-menu-org", children: [
1333
+ /* @__PURE__ */ jsx(IconBuilding, {}),
1334
+ /* @__PURE__ */ jsx("span", { children: p.user.org })
1335
+ ] }) : null
1277
1336
  ] })
1278
1337
  ] }),
1279
1338
  /* @__PURE__ */ jsx("div", { class: "bas-sep" }),
@@ -1995,6 +2054,6 @@ function ConfirmDialog(p) {
1995
2054
  );
1996
2055
  }
1997
2056
 
1998
- export { AVATAR_MAX_BYTES, AVATAR_TYPES, AccountError, AccountPage, AnalyticsError, AnalyticsPage, AppShell, Avatar, ConfirmDialog, NotificationBell, PageHeader, PageTabs, Sidebar, TEXT, TenantLoadError, TenantSwitcher, TopBar, UserMenu, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
2057
+ export { AVATAR_MAX_BYTES, AVATAR_TYPES, AccountError, AccountPage, AnalyticsError, AnalyticsPage, AppShell, Avatar, ConfirmDialog, ItemActionsMenu, NotificationBell, PageHeader, PageTabs, Sidebar, TEXT, TenantLoadError, TenantSwitcher, TopBar, UserMenu, accountErrorKind, activeNavLabel, analyticsErrorKind, badgeLabel, changePct, checkAvatarFile, copenhagenDay, createFetchAccountAdapter, createFetchAnalyticsAdapter, createFetchNotificationSource, createFetchTenantAdapter, createMemoryNotificationSource, defaultReauthHref, fillDays, initials, isActivePath, isExternal, readFlag, readSet, relativeTime, safeNavigate, useIsMobile, writeFlag, writeSet };
1999
2058
  //# sourceMappingURL=preact.js.map
2000
2059
  //# sourceMappingURL=preact.js.map