@the-portland-company/shell 0.16.0 → 0.16.2

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/native.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType, CSSProperties } from 'react';
3
3
  import { T as TopBarIconSlot } from './topbarIconSlot-FunwEKO9.cjs';
4
+ import { ThemePreference as ThemePreference$1 } from './theme-preference.cjs';
4
5
 
5
6
  declare function topBarIconButtonClass(tone?: 'default' | 'warning' | 'danger'): string;
6
7
  /** The shared count badge — one definition, used by every slot. */
@@ -27,7 +28,7 @@ declare function TopBarWidgetSlot({ title, description, children, }: {
27
28
  description?: string;
28
29
  children: ReactNode;
29
30
  }): react_jsx_runtime.JSX.Element;
30
- type ThemePreference = 'light' | 'system' | 'dark';
31
+ type ThemePreference = ThemePreference$1;
31
32
  declare function NavThemeControl({ value, onChange, }: {
32
33
  value: ThemePreference;
33
34
  onChange: (next: ThemePreference) => void;
@@ -101,6 +102,10 @@ declare function NavDisplayAction({ value, onChange, }: {
101
102
  value: NavDisplayMode;
102
103
  onChange: (next: NavDisplayMode) => void;
103
104
  }): react_jsx_runtime.JSX.Element;
105
+ declare function useThemePreference(): {
106
+ value: ThemePreference;
107
+ setValue: (next: ThemePreference) => void;
108
+ };
104
109
 
105
110
  /**
106
111
  * Icon slots used by the standard menu. The shell stays icon-library-agnostic:
@@ -385,4 +390,4 @@ type NavAppShellProps = {
385
390
  */
386
391
  declare function NavAppShell({ sidebar, topBar, children }: NavAppShellProps): react_jsx_runtime.JSX.Element;
387
392
 
388
- export { type ChromeNotificationAlert, type ChromeNotificationReply, type ChromeNotificationsPayload, type ChromeOrgSummary, type CommunicationsFuelStatus, FuelAction, type FuelChannelStatus, NavAppShell, type NavAppShellProps, type NavBrand, type NavChromeFetcher, NavChromeProvider, NavDisplayAction, type NavDisplayMode, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavLinkedAccount, type NavMode, type NavOrg, type NavOrgOption, type NavRoleBadge, NavSidebar, type NavSidebarProps, type NavTab, NavTabStrip, type NavTabStripProps, NavThemeControl, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, type NavUserMenu, NotificationsAction, type StandardNavIconKey, type StandardNavOptions, SupportAction, type ThemePreference, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs };
393
+ export { type ChromeNotificationAlert, type ChromeNotificationReply, type ChromeNotificationsPayload, type ChromeOrgSummary, type CommunicationsFuelStatus, FuelAction, type FuelChannelStatus, NavAppShell, type NavAppShellProps, type NavBrand, type NavChromeFetcher, NavChromeProvider, NavDisplayAction, type NavDisplayMode, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavLinkedAccount, type NavMode, type NavOrg, type NavOrgOption, type NavRoleBadge, NavSidebar, type NavSidebarProps, type NavTab, NavTabStrip, type NavTabStripProps, NavThemeControl, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, type NavUserMenu, NotificationsAction, type StandardNavIconKey, type StandardNavOptions, SupportAction, type ThemePreference, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs, useThemePreference };
package/dist/native.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, ComponentType, CSSProperties } from 'react';
3
3
  import { T as TopBarIconSlot } from './topbarIconSlot-FunwEKO9.js';
4
+ import { ThemePreference as ThemePreference$1 } from './theme-preference.js';
4
5
 
5
6
  declare function topBarIconButtonClass(tone?: 'default' | 'warning' | 'danger'): string;
6
7
  /** The shared count badge — one definition, used by every slot. */
@@ -27,7 +28,7 @@ declare function TopBarWidgetSlot({ title, description, children, }: {
27
28
  description?: string;
28
29
  children: ReactNode;
29
30
  }): react_jsx_runtime.JSX.Element;
30
- type ThemePreference = 'light' | 'system' | 'dark';
31
+ type ThemePreference = ThemePreference$1;
31
32
  declare function NavThemeControl({ value, onChange, }: {
32
33
  value: ThemePreference;
33
34
  onChange: (next: ThemePreference) => void;
@@ -101,6 +102,10 @@ declare function NavDisplayAction({ value, onChange, }: {
101
102
  value: NavDisplayMode;
102
103
  onChange: (next: NavDisplayMode) => void;
103
104
  }): react_jsx_runtime.JSX.Element;
105
+ declare function useThemePreference(): {
106
+ value: ThemePreference;
107
+ setValue: (next: ThemePreference) => void;
108
+ };
104
109
 
105
110
  /**
106
111
  * Icon slots used by the standard menu. The shell stays icon-library-agnostic:
@@ -385,4 +390,4 @@ type NavAppShellProps = {
385
390
  */
386
391
  declare function NavAppShell({ sidebar, topBar, children }: NavAppShellProps): react_jsx_runtime.JSX.Element;
387
392
 
388
- export { type ChromeNotificationAlert, type ChromeNotificationReply, type ChromeNotificationsPayload, type ChromeOrgSummary, type CommunicationsFuelStatus, FuelAction, type FuelChannelStatus, NavAppShell, type NavAppShellProps, type NavBrand, type NavChromeFetcher, NavChromeProvider, NavDisplayAction, type NavDisplayMode, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavLinkedAccount, type NavMode, type NavOrg, type NavOrgOption, type NavRoleBadge, NavSidebar, type NavSidebarProps, type NavTab, NavTabStrip, type NavTabStripProps, NavThemeControl, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, type NavUserMenu, NotificationsAction, type StandardNavIconKey, type StandardNavOptions, SupportAction, type ThemePreference, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs };
393
+ export { type ChromeNotificationAlert, type ChromeNotificationReply, type ChromeNotificationsPayload, type ChromeOrgSummary, type CommunicationsFuelStatus, FuelAction, type FuelChannelStatus, NavAppShell, type NavAppShellProps, type NavBrand, type NavChromeFetcher, NavChromeProvider, NavDisplayAction, type NavDisplayMode, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavLinkedAccount, type NavMode, type NavOrg, type NavOrgOption, type NavRoleBadge, NavSidebar, type NavSidebarProps, type NavTab, NavTabStrip, type NavTabStripProps, NavThemeControl, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, type NavUserMenu, NotificationsAction, type StandardNavIconKey, type StandardNavOptions, SupportAction, type ThemePreference, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs, useThemePreference };
package/dist/native.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
  import { useTopBarIconSlotGlyph, resolveBrandGlyph } from './chunk-NV6WHTEZ.js';
3
- import { createContext, useState, useEffect, useCallback, useMemo, useContext, useRef } from 'react';
3
+ import { readThemeCookie, THEME_STORAGE_KEY, isThemePreference, applyThemePreference, writeThemeCookie } from './chunk-ZZWM7BZZ.js';
4
+ import { createContext, useState, useEffect, useCallback, useContext, useRef, useMemo } from 'react';
5
+ import { createPortal } from 'react-dom';
4
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
7
 
6
8
  function topBarIconButtonClass(tone = "default") {
@@ -431,66 +433,69 @@ function SupportAction() {
431
433
  ]
432
434
  }
433
435
  ),
434
- open ? /* @__PURE__ */ jsx(
435
- "div",
436
- {
437
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
438
- role: "dialog",
439
- "aria-modal": "true",
440
- "aria-label": "Support request",
441
- onMouseDown: (e) => {
442
- if (e.target === e.currentTarget) close();
443
- },
444
- children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md rounded-2xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-6 shadow-[var(--shadow-soft)]", children: [
445
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: "Contact Support" }),
446
- state === "sent" ? /* @__PURE__ */ jsxs(Fragment, { children: [
447
- /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-[color:var(--muted)]", children: "Your request was sent. The team will reply by email." }),
448
- /* @__PURE__ */ jsx("div", { className: "mt-5 flex justify-end", children: /* @__PURE__ */ jsx(
449
- "button",
450
- {
451
- type: "button",
452
- onClick: close,
453
- className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90",
454
- children: "Done"
455
- }
456
- ) })
457
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
458
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-[color:var(--muted)]", children: "Describe the problem or question. Your account details ride along automatically." }),
459
- /* @__PURE__ */ jsx(
460
- "textarea",
461
- {
462
- value: message,
463
- onChange: (e) => setMessage(e.target.value),
464
- rows: 5,
465
- placeholder: "How can we help?",
466
- className: "mt-4 w-full resize-y rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-race-red)]"
467
- }
468
- ),
469
- state === "error" ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-[color:var(--color-race-red)]", children: "Sending failed \u2014 try again." }) : null,
470
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
471
- /* @__PURE__ */ jsx(
436
+ open && typeof document !== "undefined" ? createPortal(
437
+ /* @__PURE__ */ jsx(
438
+ "div",
439
+ {
440
+ className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
441
+ role: "dialog",
442
+ "aria-modal": "true",
443
+ "aria-label": "Support request",
444
+ onMouseDown: (e) => {
445
+ if (e.target === e.currentTarget) close();
446
+ },
447
+ children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md rounded-2xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-6 shadow-[var(--shadow-soft)]", children: [
448
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: "Contact Support" }),
449
+ state === "sent" ? /* @__PURE__ */ jsxs(Fragment, { children: [
450
+ /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-[color:var(--muted)]", children: "Your request was sent. The team will reply by email." }),
451
+ /* @__PURE__ */ jsx("div", { className: "mt-5 flex justify-end", children: /* @__PURE__ */ jsx(
472
452
  "button",
473
453
  {
474
454
  type: "button",
475
455
  onClick: close,
476
- className: "rounded-full border border-[color:var(--line)] px-4 py-2 text-sm font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
477
- children: "Cancel"
456
+ className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white hover:opacity-90",
457
+ children: "Done"
478
458
  }
479
- ),
459
+ ) })
460
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
461
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-[color:var(--muted)]", children: "Describe the problem or question. Your account details ride along automatically." }),
480
462
  /* @__PURE__ */ jsx(
481
- "button",
463
+ "textarea",
482
464
  {
483
- type: "button",
484
- onClick: submit,
485
- disabled: !message.trim() || state === "sending",
486
- className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
487
- children: state === "sending" ? "Sending\u2026" : "Send request"
465
+ value: message,
466
+ onChange: (e) => setMessage(e.target.value),
467
+ rows: 5,
468
+ placeholder: "How can we help?",
469
+ className: "mt-4 w-full resize-y rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-race-red)]"
488
470
  }
489
- )
471
+ ),
472
+ state === "error" ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-[color:var(--color-race-red)]", children: "Sending failed \u2014 try again." }) : null,
473
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
474
+ /* @__PURE__ */ jsx(
475
+ "button",
476
+ {
477
+ type: "button",
478
+ onClick: close,
479
+ className: "rounded-full border border-[color:var(--line)] px-4 py-2 text-sm font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
480
+ children: "Cancel"
481
+ }
482
+ ),
483
+ /* @__PURE__ */ jsx(
484
+ "button",
485
+ {
486
+ type: "button",
487
+ onClick: submit,
488
+ disabled: !message.trim() || state === "sending",
489
+ className: "rounded-full bg-[color:var(--color-race-red)] px-4 py-2 text-sm font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
490
+ children: state === "sending" ? "Sending\u2026" : "Send request"
491
+ }
492
+ )
493
+ ] })
490
494
  ] })
491
495
  ] })
492
- ] })
493
- }
496
+ }
497
+ ),
498
+ document.body
494
499
  ) : null
495
500
  ] });
496
501
  }
@@ -531,6 +536,96 @@ function NavDisplayAction({
531
536
  }
532
537
  );
533
538
  }
539
+ function useThemePreference() {
540
+ const ctx = useContext(NavChromeContext);
541
+ const fetchJson = ctx?.fetchJson ?? null;
542
+ const [value, setValueState] = useState("system");
543
+ const reconciled = useRef(false);
544
+ useEffect(() => {
545
+ const cookie = readThemeCookie();
546
+ let ls = null;
547
+ try {
548
+ ls = localStorage.getItem(THEME_STORAGE_KEY);
549
+ } catch {
550
+ }
551
+ const pref = cookie ?? (isThemePreference(ls) ? ls : null) ?? "system";
552
+ setValueState(pref);
553
+ applyThemePreference(pref);
554
+ }, []);
555
+ useEffect(() => {
556
+ if (!fetchJson || reconciled.current) return;
557
+ reconciled.current = true;
558
+ fetchJson("me").then((data) => {
559
+ const pref = data?.themePreference;
560
+ if (isThemePreference(pref)) {
561
+ setValueState(pref);
562
+ applyThemePreference(pref);
563
+ writeThemeCookie(pref);
564
+ try {
565
+ localStorage.setItem(THEME_STORAGE_KEY, pref);
566
+ } catch {
567
+ }
568
+ }
569
+ }).catch(() => {
570
+ });
571
+ }, [fetchJson]);
572
+ useEffect(() => {
573
+ const onStorage = (e) => {
574
+ if (e.key === THEME_STORAGE_KEY && isThemePreference(e.newValue)) {
575
+ setValueState(e.newValue);
576
+ applyThemePreference(e.newValue);
577
+ }
578
+ };
579
+ const onWake = () => {
580
+ const cookie = readThemeCookie();
581
+ if (cookie) {
582
+ setValueState((prev) => {
583
+ if (cookie !== prev) applyThemePreference(cookie);
584
+ return cookie;
585
+ });
586
+ }
587
+ };
588
+ window.addEventListener("storage", onStorage);
589
+ window.addEventListener("focus", onWake);
590
+ document.addEventListener("visibilitychange", onWake);
591
+ return () => {
592
+ window.removeEventListener("storage", onStorage);
593
+ window.removeEventListener("focus", onWake);
594
+ document.removeEventListener("visibilitychange", onWake);
595
+ };
596
+ }, []);
597
+ useEffect(() => {
598
+ if (value !== "system") return;
599
+ let mq;
600
+ try {
601
+ mq = window.matchMedia("(prefers-color-scheme: dark)");
602
+ } catch {
603
+ return;
604
+ }
605
+ const onChange = () => applyThemePreference("system");
606
+ mq.addEventListener("change", onChange);
607
+ return () => mq.removeEventListener("change", onChange);
608
+ }, [value]);
609
+ const setValue = useCallback(
610
+ (next) => {
611
+ setValueState(next);
612
+ applyThemePreference(next);
613
+ writeThemeCookie(next);
614
+ try {
615
+ localStorage.setItem(THEME_STORAGE_KEY, next);
616
+ } catch {
617
+ }
618
+ fetchJson?.("me/preferences", {
619
+ method: "PATCH",
620
+ headers: { "Content-Type": "application/json" },
621
+ body: JSON.stringify({ themePreference: next })
622
+ }).catch(() => {
623
+ });
624
+ },
625
+ [fetchJson]
626
+ );
627
+ return { value, setValue };
628
+ }
534
629
 
535
630
  // src/native/standardNav.ts
536
631
  var CONTACTS_DEFAULT_CHILDREN = [
@@ -807,7 +902,7 @@ function NavSidebar({
807
902
  ] })
808
903
  ] }),
809
904
  footer ? /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-[color:var(--line)] px-4 py-3", children: [
810
- footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-xs", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
905
+ footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-center gap-1.5 text-xs", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
811
906
  idx > 0 ? /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
812
907
  /* @__PURE__ */ jsx(
813
908
  "a",
@@ -839,14 +934,12 @@ function NavSidebar({
839
934
  ] })
840
935
  ] }),
841
936
  footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
842
- /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
843
937
  footer.commitDate ? /* @__PURE__ */ jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
844
938
  month: "short",
845
939
  day: "numeric"
846
940
  }) }) : null,
847
- /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
848
- footer.commitAuthor ? /* @__PURE__ */ jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null,
849
- /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" })
941
+ footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
942
+ footer.commitAuthor ? /* @__PURE__ */ jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null
850
943
  ] }) : null
851
944
  ] })
852
945
  ] }) : null
@@ -1359,6 +1452,6 @@ function NavAppShell({ sidebar, topBar, children }) {
1359
1452
  ] });
1360
1453
  }
1361
1454
 
1362
- export { FuelAction, NavAppShell, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SupportAction, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs };
1455
+ export { FuelAction, NavAppShell, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SupportAction, TopBarCountBadge, TopBarTooltip, TopBarWidgetSlot, envChipClasses, isNavItemActive, standardPolitogyNav, topBarIconButtonClass, useChromeOrgs, useThemePreference };
1363
1456
  //# sourceMappingURL=native.js.map
1364
1457
  //# sourceMappingURL=native.js.map