@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/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
- return /* @__PURE__ */ jsxRuntime.jsx("div", { class: "bas-seg", "data-testid": props.testid, role: "group", 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)) });
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("a", { class: "bas-mi", href: p.accountHref, "data-testid": "user-menu-account", role: "menuitem", children: [
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;