@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/README.md +44 -0
- package/css/app-shell.css +39 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +207 -3
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.d.cts +48 -1
- package/dist/preact.d.ts +48 -1
- package/dist/preact.js +207 -3
- package/dist/preact.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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,
|