@broberg/app-shell 0.2.3 → 0.3.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 +24 -0
- package/css/app-shell.css +23 -4
- package/dist/index.cjs +17 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +63 -9
- package/dist/preact.cjs.map +1 -1
- package/dist/preact.d.cts +31 -3
- package/dist/preact.d.ts +31 -3
- package/dist/preact.js +63 -10
- package/dist/preact.js.map +1 -1
- package/package.json +1 -1
package/dist/preact.cjs
CHANGED
|
@@ -25,6 +25,18 @@ function safeNavigate(target) {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
function isExternal(href, origin = typeof location !== "undefined" ? location.origin : void 0) {
|
|
29
|
+
if (typeof href !== "string") return false;
|
|
30
|
+
const h = href.trim();
|
|
31
|
+
if (!origin) return /^(https?:)?\/\//i.test(h);
|
|
32
|
+
let u;
|
|
33
|
+
try {
|
|
34
|
+
u = new URL(h, origin);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return (u.protocol === "http:" || u.protocol === "https:") && u.origin !== origin;
|
|
39
|
+
}
|
|
28
40
|
function activeNavLabel(groups, footer, currentPath) {
|
|
29
41
|
let best;
|
|
30
42
|
const visit = (it) => {
|
|
@@ -173,8 +185,9 @@ var TEXT = {
|
|
|
173
185
|
da: {
|
|
174
186
|
openMenu: "\xC5bn menu",
|
|
175
187
|
closeMenu: "Luk menu",
|
|
176
|
-
couldNotMarkAll: "Kunne ikke markere alle som l\xE6st. Pr\xF8v igen.",
|
|
177
188
|
home: "Forside",
|
|
189
|
+
language: "Sprog",
|
|
190
|
+
couldNotMarkAll: "Kunne ikke markere alle som l\xE6st. Pr\xF8v igen.",
|
|
178
191
|
collapse: "Skjul sidemenu",
|
|
179
192
|
expand: "Vis sidemenu",
|
|
180
193
|
notifications: "Notifikationer",
|
|
@@ -200,8 +213,9 @@ var TEXT = {
|
|
|
200
213
|
en: {
|
|
201
214
|
openMenu: "Open menu",
|
|
202
215
|
closeMenu: "Close menu",
|
|
203
|
-
couldNotMarkAll: "Could not mark all as read. Try again.",
|
|
204
216
|
home: "Home",
|
|
217
|
+
language: "Language",
|
|
218
|
+
couldNotMarkAll: "Could not mark all as read. Try again.",
|
|
205
219
|
collapse: "Hide sidebar",
|
|
206
220
|
expand: "Show sidebar",
|
|
207
221
|
notifications: "Notifications",
|
|
@@ -240,6 +254,9 @@ function useIsMobile() {
|
|
|
240
254
|
}
|
|
241
255
|
function NavLink(props) {
|
|
242
256
|
const { href, onNavigate, onAfter, children, ...rest } = props;
|
|
257
|
+
if (isExternal(href)) {
|
|
258
|
+
return /* @__PURE__ */ jsxRuntime.jsx("a", { ...rest, href, target: "_blank", rel: "noopener noreferrer", onClick: () => onAfter?.(), children });
|
|
259
|
+
}
|
|
243
260
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
244
261
|
"a",
|
|
245
262
|
{
|
|
@@ -415,7 +432,7 @@ function Sidebar(p) {
|
|
|
415
432
|
"aria-label": "Navigation",
|
|
416
433
|
children: [
|
|
417
434
|
p.header || p.brandMark ? /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-sidebar__head", children: [
|
|
418
|
-
p.header ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sidebar__brand", children: p.homeHref ? /* @__PURE__ */ jsxRuntime.jsx(NavLink, { href: p.homeHref, onNavigate: p.onNavigate, onAfter: close, class: "bas-home", "data-testid": "sidebar-brand-home", children: p.header }) : p.header }) : null,
|
|
435
|
+
p.header ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sidebar__brand", children: p.homeHref ? /* @__PURE__ */ jsxRuntime.jsx(NavLink, { href: p.homeHref, onNavigate: p.onNavigate, onAfter: close, class: "bas-home", "data-testid": p.brandTestId ?? "sidebar-brand-home", children: p.header }) : p.header }) : null,
|
|
419
436
|
p.brandMark ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sidebar__mark", "data-testid": "sidebar-brand-mark", children: p.homeHref ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
420
437
|
NavLink,
|
|
421
438
|
{
|
|
@@ -532,7 +549,9 @@ function NotificationBell({ source, onNavigate, lang }) {
|
|
|
532
549
|
await shell$1.refresh();
|
|
533
550
|
}
|
|
534
551
|
const target = safeNavigate(n.navigate);
|
|
535
|
-
if (target) {
|
|
552
|
+
if (target && isExternal(target)) {
|
|
553
|
+
if (typeof window !== "undefined") window.open(target, "_blank", "noopener,noreferrer");
|
|
554
|
+
} else if (target) {
|
|
536
555
|
if (onNavigate) onNavigate(target);
|
|
537
556
|
else if (typeof window !== "undefined") window.location.assign(target);
|
|
538
557
|
}
|
|
@@ -608,7 +627,16 @@ function Avatar({ user, size = 28 }) {
|
|
|
608
627
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-avatar bas-avatar--initials", style, "data-testid": "avatar-initials", "aria-hidden": "true", children: initials(user.name, user.email) });
|
|
609
628
|
}
|
|
610
629
|
function Segmented(props) {
|
|
611
|
-
|
|
630
|
+
const onKeyDown = (e) => {
|
|
631
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
632
|
+
const buttons = [...e.currentTarget.querySelectorAll("button")];
|
|
633
|
+
const i = buttons.indexOf(document.activeElement);
|
|
634
|
+
if (i < 0) return;
|
|
635
|
+
e.preventDefault();
|
|
636
|
+
const next = (i + (e.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length;
|
|
637
|
+
buttons[next].focus();
|
|
638
|
+
};
|
|
639
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-seg", "data-testid": props.testid, role: "group", "aria-label": props.label, onKeyDown, children: props.options.map(([v, label]) => /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-pressed": props.value === v, "data-testid": `${props.testid}-${v}`, onClick: () => props.onChange(v), children: label }, v)) });
|
|
612
640
|
}
|
|
613
641
|
function UserMenu(p) {
|
|
614
642
|
const t = TEXT[p.lang];
|
|
@@ -677,7 +705,7 @@ function UserMenu(p) {
|
|
|
677
705
|
] })
|
|
678
706
|
] }),
|
|
679
707
|
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sep" }),
|
|
680
|
-
p.accountHref ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
708
|
+
p.accountHref ? /* @__PURE__ */ jsxRuntime.jsxs(NavLink, { href: p.accountHref, onNavigate: p.onNavigate, onAfter: () => setOpen(false), class: "bas-mi", "data-testid": "user-menu-account", role: "menuitem", children: [
|
|
681
709
|
/* @__PURE__ */ jsxRuntime.jsx(IconUser, {}),
|
|
682
710
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.account })
|
|
683
711
|
] }) : null,
|
|
@@ -736,6 +764,22 @@ function UserMenu(p) {
|
|
|
736
764
|
] })
|
|
737
765
|
] })
|
|
738
766
|
] }) : null,
|
|
767
|
+
p.language ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
768
|
+
p.appearance === false ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sep" }) : null,
|
|
769
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-prefs", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-pref", children: [
|
|
770
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.language }),
|
|
771
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
772
|
+
Segmented,
|
|
773
|
+
{
|
|
774
|
+
testid: "user-menu-language",
|
|
775
|
+
label: t.language,
|
|
776
|
+
value: p.language.value,
|
|
777
|
+
options: p.language.options.map((o) => [o.id, o.label]),
|
|
778
|
+
onChange: p.language.onChange
|
|
779
|
+
}
|
|
780
|
+
)
|
|
781
|
+
] }) })
|
|
782
|
+
] }) : null,
|
|
739
783
|
p.onSignOut ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
740
784
|
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-sep" }),
|
|
741
785
|
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", class: "bas-mi is-danger", role: "menuitem", "data-testid": "topbar-sign-out", disabled: busy, onClick: () => void signOut(), children: [
|
|
@@ -785,6 +829,8 @@ function PageTabs(p) {
|
|
|
785
829
|
function AppShell(p) {
|
|
786
830
|
const prefix = p.storageKey ?? "broberg-app-shell";
|
|
787
831
|
const mode = p.mobile ?? "drawer";
|
|
832
|
+
const layout = p.layout ?? "inset";
|
|
833
|
+
const inset = layout === "inset";
|
|
788
834
|
const collapse = p.collapse ?? (mode === "rail" ? "icon" : "offcanvas");
|
|
789
835
|
const [collapsed, setCollapsed] = hooks.useState(() => readFlag(`${prefix}.collapsed`, false));
|
|
790
836
|
const [drawer, setDrawer] = hooks.useState(false);
|
|
@@ -800,8 +846,8 @@ function AppShell(p) {
|
|
|
800
846
|
const expanded = drawerMode ? drawer : !collapsed;
|
|
801
847
|
const triggerLabel = drawerMode ? drawer ? t.closeMenu : t.openMenu : collapsed ? t.expand : t.collapse;
|
|
802
848
|
const heading = p.title ?? activeNavLabel(p.groups, p.footer, p.currentPath);
|
|
803
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-root", "data-testid": "app-shell", "data-mobile": mode, children: [
|
|
804
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
849
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { class: "bas-root", "data-testid": "app-shell", "data-mobile": mode, "data-layout": layout, children: [
|
|
850
|
+
inset ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
805
851
|
TopBar,
|
|
806
852
|
{
|
|
807
853
|
brand: p.brand,
|
|
@@ -831,7 +877,8 @@ function AppShell(p) {
|
|
|
831
877
|
mobileOpen: drawer,
|
|
832
878
|
onClose: () => setDrawer(false),
|
|
833
879
|
mobile: mode,
|
|
834
|
-
header: mode === "drawer" ? p.brand : void 0,
|
|
880
|
+
header: inset || mode === "drawer" ? p.brand : void 0,
|
|
881
|
+
brandTestId: inset ? "brand-home" : void 0,
|
|
835
882
|
groupsKey: `${prefix}.groups`,
|
|
836
883
|
itemsKey: `${prefix}.items`,
|
|
837
884
|
lang: p.lang
|
|
@@ -855,6 +902,12 @@ function AppShell(p) {
|
|
|
855
902
|
heading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
856
903
|
/* @__PURE__ */ jsxRuntime.jsx("span", { class: "bas-contenthead__sep", "data-testid": "content-header-separator", "aria-hidden": "true" }),
|
|
857
904
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { class: "bas-contenthead__title", "data-testid": "content-header-title", children: heading })
|
|
905
|
+
] }) : null,
|
|
906
|
+
inset ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
907
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-spacer" }),
|
|
908
|
+
p.actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-contenthead__actions", children: p.actions }) : null,
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx(NotificationBell, { source: p.notifications, onNavigate: p.onNavigate, lang: p.lang }),
|
|
910
|
+
p.user ? /* @__PURE__ */ jsxRuntime.jsx(UserMenu, { user: p.user, ...p.userMenu, onNavigate: p.onNavigate, lang: p.lang }) : null
|
|
858
911
|
] }) : null
|
|
859
912
|
] }),
|
|
860
913
|
p.children
|
|
@@ -878,6 +931,7 @@ exports.createFetchNotificationSource = createFetchNotificationSource;
|
|
|
878
931
|
exports.createMemoryNotificationSource = createMemoryNotificationSource;
|
|
879
932
|
exports.initials = initials;
|
|
880
933
|
exports.isActivePath = isActivePath;
|
|
934
|
+
exports.isExternal = isExternal;
|
|
881
935
|
exports.readFlag = readFlag;
|
|
882
936
|
exports.readSet = readSet;
|
|
883
937
|
exports.relativeTime = relativeTime;
|