@forjio/portal-ui 0.4.1 → 0.5.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/Sidebar.cjs CHANGED
@@ -46,6 +46,7 @@ const DEFAULT_DROPDOWN_LINKS = [
46
46
  function Sidebar({
47
47
  brandSlug,
48
48
  brandName,
49
+ brandTag,
49
50
  brandHref = "/dashboard",
50
51
  brandColor,
51
52
  brandColorSoft,
@@ -60,7 +61,8 @@ function Sidebar({
60
61
  onLogout,
61
62
  open,
62
63
  onClose,
63
- dropdownLinks = DEFAULT_DROPDOWN_LINKS
64
+ dropdownLinks = DEFAULT_DROPDOWN_LINKS,
65
+ portals
64
66
  }) {
65
67
  const pathname = (0, import_navigation.usePathname)() ?? "";
66
68
  const workspaceMode = workspaces !== void 0;
@@ -131,15 +133,40 @@ function Sidebar({
131
133
  display: "flex",
132
134
  alignItems: "center",
133
135
  gap: 8,
134
- fontSize: 18,
135
- fontWeight: 700,
136
- letterSpacing: "-0.02em",
137
136
  textDecoration: "none",
138
137
  color: "inherit"
139
138
  },
140
139
  children: [
141
140
  brandIcon,
142
- brandName
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
143
+ "span",
144
+ {
145
+ style: {
146
+ fontSize: 18,
147
+ fontWeight: 700,
148
+ letterSpacing: "-0.02em",
149
+ lineHeight: 1.1
150
+ },
151
+ children: brandName
152
+ }
153
+ ),
154
+ brandTag && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
155
+ "span",
156
+ {
157
+ style: {
158
+ marginTop: 2,
159
+ fontSize: 10,
160
+ fontWeight: 600,
161
+ letterSpacing: "0.1em",
162
+ textTransform: "uppercase",
163
+ color: MUTED_SOFT,
164
+ fontFamily: MONO_FONT
165
+ },
166
+ children: brandTag
167
+ }
168
+ )
169
+ ] })
143
170
  ]
144
171
  }
145
172
  ),
@@ -173,7 +200,16 @@ function Sidebar({
173
200
  }
174
201
  ),
175
202
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, padding: "16px 10px", overflowY: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavList, { pathname, sections, onNavigate: onClose }) }),
176
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileDropdown, { user, onLogout, onNavigate: onClose, dropdownLinks })
203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
204
+ ProfileDropdown,
205
+ {
206
+ user,
207
+ onLogout,
208
+ onNavigate: onClose,
209
+ dropdownLinks,
210
+ portals
211
+ }
212
+ )
177
213
  ]
178
214
  }
179
215
  )
@@ -182,6 +218,7 @@ function Sidebar({
182
218
  const FG = "hsl(var(--foreground, 222 47% 11%))";
183
219
  const MUTED = "hsl(var(--muted-foreground, 220 9% 46%))";
184
220
  const MUTED_SOFT = "hsl(var(--muted-foreground, 220 9% 46%) / 0.6)";
221
+ const MONO_FONT = "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)";
185
222
  function itemLinkStyle(active) {
186
223
  return {
187
224
  display: "flex",
@@ -308,7 +345,8 @@ function NavModuleAccordion({
308
345
  textTransform: "uppercase",
309
346
  color: MUTED_SOFT,
310
347
  padding: "8px 10px 4px 32px",
311
- fontWeight: 600
348
+ fontWeight: 600,
349
+ fontFamily: MONO_FONT
312
350
  };
313
351
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "block" }, children: [
314
352
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -372,7 +410,8 @@ function NavList({
372
410
  textTransform: "uppercase",
373
411
  color: MUTED_SOFT,
374
412
  padding: "0 10px 6px",
375
- fontWeight: 600
413
+ fontWeight: 600,
414
+ fontFamily: MONO_FONT
376
415
  },
377
416
  children: section.label
378
417
  }
@@ -648,7 +687,8 @@ function ProfileDropdown({
648
687
  user,
649
688
  onLogout,
650
689
  onNavigate,
651
- dropdownLinks
690
+ dropdownLinks,
691
+ portals
652
692
  }) {
653
693
  const [open, setOpen] = (0, import_react.useState)(false);
654
694
  const ref = (0, import_react.useRef)(null);
@@ -722,6 +762,69 @@ function ProfileDropdown({
722
762
  ]
723
763
  }
724
764
  ),
765
+ portals && portals.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
767
+ "div",
768
+ {
769
+ style: {
770
+ fontSize: 9.5,
771
+ letterSpacing: "0.12em",
772
+ textTransform: "uppercase",
773
+ color: MUTED_SOFT,
774
+ fontWeight: 600,
775
+ fontFamily: MONO_FONT,
776
+ padding: "8px 12px 2px"
777
+ },
778
+ children: "Switch portal"
779
+ }
780
+ ),
781
+ portals.map(
782
+ (portal) => portal.current ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
783
+ "div",
784
+ {
785
+ "aria-current": "page",
786
+ style: {
787
+ ...itemStyle,
788
+ color: "var(--brand-color)",
789
+ fontWeight: 600,
790
+ cursor: "default"
791
+ },
792
+ children: [
793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
794
+ "span",
795
+ {
796
+ "aria-hidden": true,
797
+ style: {
798
+ width: 14,
799
+ display: "inline-flex",
800
+ justifyContent: "center"
801
+ },
802
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Check, { size: 14 })
803
+ }
804
+ ),
805
+ portal.label
806
+ ]
807
+ },
808
+ portal.href
809
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
810
+ import_link.default,
811
+ {
812
+ href: portal.href,
813
+ onClick: () => {
814
+ setOpen(false);
815
+ onNavigate?.();
816
+ },
817
+ style: itemStyle,
818
+ children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { width: 14 } }),
820
+ portal.label
821
+ ]
822
+ },
823
+ portal.href
824
+ )
825
+ ),
826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { borderTop: "1px solid hsl(var(--border, 220 14% 90%))", margin: "4px 0" } })
827
+ ] }),
725
828
  dropdownLinks.map((link) => {
726
829
  const Icon = link.icon;
727
830
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","module"]}
1
+ {"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, Check, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalLink,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Optional portal identifier shown as a small uppercase mono\n * subtitle directly below the brand name, e.g. \"Creator\" /\n * \"Affiliator\" for ripllo's sibling portals. Omit on portals that\n * don't need to distinguish themselves (a single-portal product, or\n * ripllo's default merchant surface). */\n brandTag?: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n /** Optional sibling portals — when provided and non-empty, the\n * profile dropdown renders a \"Switch portal\" section listing each\n * one. The entry flagged `current` is shown as the active portal\n * (non-clickable); the rest are links. Omit/empty → no switcher.\n * Ripllo passes all three of its portals on every ripllo portal. */\n portals?: PortalLink[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandTag,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n portals,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n <span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>\n <span\n style={{\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n lineHeight: 1.1,\n }}\n >\n {brandName}\n </span>\n {brandTag && (\n <span\n style={{\n marginTop: 2,\n fontSize: 10,\n fontWeight: 600,\n letterSpacing: '0.1em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n fontFamily: MONO_FONT,\n }}\n >\n {brandTag}\n </span>\n )}\n </span>\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown\n user={user}\n onLogout={onLogout}\n onNavigate={onClose}\n dropdownLinks={dropdownLinks}\n portals={portals}\n />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n/** Monospace stack for label-style chrome (section headings, the\n * brand tag). Pinned so it renders mono on every host regardless of\n * the host page's inherited font. */\nconst MONO_FONT = 'var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n fontFamily: MONO_FONT,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n fontFamily: MONO_FONT,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n portals,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n portals?: PortalLink[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {portals && portals.length > 0 && (\n <>\n <div\n style={{\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n fontWeight: 600,\n fontFamily: MONO_FONT,\n padding: '8px 12px 2px',\n }}\n >\n Switch portal\n </div>\n {portals.map((portal) =>\n portal.current ? (\n <div\n key={portal.href}\n aria-current=\"page\"\n style={{\n ...itemStyle,\n color: 'var(--brand-color)',\n fontWeight: 600,\n cursor: 'default',\n }}\n >\n <span\n aria-hidden\n style={{\n width: 14,\n display: 'inline-flex',\n justifyContent: 'center',\n }}\n >\n <Check size={14} />\n </span>\n {portal.label}\n </div>\n ) : (\n <Link\n key={portal.href}\n href={portal.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <span aria-hidden style={{ width: 14 }} />\n {portal.label}\n </Link>\n ),\n )}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n </>\n )}\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","module"]}
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { WorkspacePersistMode, PortalWorkspace, NavSection, SessionUser, LucideIcon } from './types.cjs';
2
+ import { WorkspacePersistMode, PortalWorkspace, NavSection, SessionUser, LucideIcon, PortalLink } from './types.cjs';
3
3
 
4
4
  /**
5
5
  * Forjio family sidebar — workspace switcher on top, nav sections in
@@ -15,6 +15,12 @@ interface SidebarProps {
15
15
  brandSlug: string;
16
16
  /** Display name shown at the top of the sidebar. */
17
17
  brandName: string;
18
+ /** Optional portal identifier shown as a small uppercase mono
19
+ * subtitle directly below the brand name, e.g. "Creator" /
20
+ * "Affiliator" for ripllo's sibling portals. Omit on portals that
21
+ * don't need to distinguish themselves (a single-portal product, or
22
+ * ripllo's default merchant surface). */
23
+ brandTag?: string;
18
24
  /** Where the brand wordmark / logo links — the portal's home. Default
19
25
  * `/dashboard`. No-workspace portals set their own home, e.g.
20
26
  * `/creators/dashboard` or a storefront buyer account root. */
@@ -71,7 +77,13 @@ interface SidebarProps {
71
77
  label: string;
72
78
  icon: LucideIcon;
73
79
  }[];
80
+ /** Optional sibling portals — when provided and non-empty, the
81
+ * profile dropdown renders a "Switch portal" section listing each
82
+ * one. The entry flagged `current` is shown as the active portal
83
+ * (non-clickable); the rest are links. Omit/empty → no switcher.
84
+ * Ripllo passes all three of its portals on every ripllo portal. */
85
+ portals?: PortalLink[];
74
86
  }
75
- declare function Sidebar({ brandSlug, brandName, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
87
+ declare function Sidebar({ brandSlug, brandName, brandTag, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, portals, }: SidebarProps): react.JSX.Element;
76
88
 
77
89
  export { Sidebar, type SidebarProps };
package/dist/Sidebar.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { WorkspacePersistMode, PortalWorkspace, NavSection, SessionUser, LucideIcon } from './types.js';
2
+ import { WorkspacePersistMode, PortalWorkspace, NavSection, SessionUser, LucideIcon, PortalLink } from './types.js';
3
3
 
4
4
  /**
5
5
  * Forjio family sidebar — workspace switcher on top, nav sections in
@@ -15,6 +15,12 @@ interface SidebarProps {
15
15
  brandSlug: string;
16
16
  /** Display name shown at the top of the sidebar. */
17
17
  brandName: string;
18
+ /** Optional portal identifier shown as a small uppercase mono
19
+ * subtitle directly below the brand name, e.g. "Creator" /
20
+ * "Affiliator" for ripllo's sibling portals. Omit on portals that
21
+ * don't need to distinguish themselves (a single-portal product, or
22
+ * ripllo's default merchant surface). */
23
+ brandTag?: string;
18
24
  /** Where the brand wordmark / logo links — the portal's home. Default
19
25
  * `/dashboard`. No-workspace portals set their own home, e.g.
20
26
  * `/creators/dashboard` or a storefront buyer account root. */
@@ -71,7 +77,13 @@ interface SidebarProps {
71
77
  label: string;
72
78
  icon: LucideIcon;
73
79
  }[];
80
+ /** Optional sibling portals — when provided and non-empty, the
81
+ * profile dropdown renders a "Switch portal" section listing each
82
+ * one. The entry flagged `current` is shown as the active portal
83
+ * (non-clickable); the rest are links. Omit/empty → no switcher.
84
+ * Ripllo passes all three of its portals on every ripllo portal. */
85
+ portals?: PortalLink[];
74
86
  }
75
- declare function Sidebar({ brandSlug, brandName, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
87
+ declare function Sidebar({ brandSlug, brandName, brandTag, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, portals, }: SidebarProps): react.JSX.Element;
76
88
 
77
89
  export { Sidebar, type SidebarProps };
package/dist/Sidebar.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
4
  import { usePathname } from "next/navigation";
5
- import { ChevronUp, ChevronDown, ChevronRight, X, LogOut, BookOpen, FileText, Shield } from "lucide-react";
5
+ import { ChevronUp, ChevronDown, ChevronRight, Check, X, LogOut, BookOpen, FileText, Shield } from "lucide-react";
6
6
  import { useEffect, useRef, useState } from "react";
7
7
  import { activeHrefFor, titleCase, writeActiveWorkspace } from "./utils";
8
8
  const DEFAULT_DROPDOWN_LINKS = [
@@ -13,6 +13,7 @@ const DEFAULT_DROPDOWN_LINKS = [
13
13
  function Sidebar({
14
14
  brandSlug,
15
15
  brandName,
16
+ brandTag,
16
17
  brandHref = "/dashboard",
17
18
  brandColor,
18
19
  brandColorSoft,
@@ -27,7 +28,8 @@ function Sidebar({
27
28
  onLogout,
28
29
  open,
29
30
  onClose,
30
- dropdownLinks = DEFAULT_DROPDOWN_LINKS
31
+ dropdownLinks = DEFAULT_DROPDOWN_LINKS,
32
+ portals
31
33
  }) {
32
34
  const pathname = usePathname() ?? "";
33
35
  const workspaceMode = workspaces !== void 0;
@@ -98,15 +100,40 @@ function Sidebar({
98
100
  display: "flex",
99
101
  alignItems: "center",
100
102
  gap: 8,
101
- fontSize: 18,
102
- fontWeight: 700,
103
- letterSpacing: "-0.02em",
104
103
  textDecoration: "none",
105
104
  color: "inherit"
106
105
  },
107
106
  children: [
108
107
  brandIcon,
109
- brandName
108
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column", minWidth: 0 }, children: [
109
+ /* @__PURE__ */ jsx(
110
+ "span",
111
+ {
112
+ style: {
113
+ fontSize: 18,
114
+ fontWeight: 700,
115
+ letterSpacing: "-0.02em",
116
+ lineHeight: 1.1
117
+ },
118
+ children: brandName
119
+ }
120
+ ),
121
+ brandTag && /* @__PURE__ */ jsx(
122
+ "span",
123
+ {
124
+ style: {
125
+ marginTop: 2,
126
+ fontSize: 10,
127
+ fontWeight: 600,
128
+ letterSpacing: "0.1em",
129
+ textTransform: "uppercase",
130
+ color: MUTED_SOFT,
131
+ fontFamily: MONO_FONT
132
+ },
133
+ children: brandTag
134
+ }
135
+ )
136
+ ] })
110
137
  ]
111
138
  }
112
139
  ),
@@ -140,7 +167,16 @@ function Sidebar({
140
167
  }
141
168
  ),
142
169
  /* @__PURE__ */ jsx("div", { style: { flex: 1, padding: "16px 10px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(NavList, { pathname, sections, onNavigate: onClose }) }),
143
- /* @__PURE__ */ jsx(ProfileDropdown, { user, onLogout, onNavigate: onClose, dropdownLinks })
170
+ /* @__PURE__ */ jsx(
171
+ ProfileDropdown,
172
+ {
173
+ user,
174
+ onLogout,
175
+ onNavigate: onClose,
176
+ dropdownLinks,
177
+ portals
178
+ }
179
+ )
144
180
  ]
145
181
  }
146
182
  )
@@ -149,6 +185,7 @@ function Sidebar({
149
185
  const FG = "hsl(var(--foreground, 222 47% 11%))";
150
186
  const MUTED = "hsl(var(--muted-foreground, 220 9% 46%))";
151
187
  const MUTED_SOFT = "hsl(var(--muted-foreground, 220 9% 46%) / 0.6)";
188
+ const MONO_FONT = "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)";
152
189
  function itemLinkStyle(active) {
153
190
  return {
154
191
  display: "flex",
@@ -275,7 +312,8 @@ function NavModuleAccordion({
275
312
  textTransform: "uppercase",
276
313
  color: MUTED_SOFT,
277
314
  padding: "8px 10px 4px 32px",
278
- fontWeight: 600
315
+ fontWeight: 600,
316
+ fontFamily: MONO_FONT
279
317
  };
280
318
  return /* @__PURE__ */ jsxs("li", { style: { display: "block" }, children: [
281
319
  /* @__PURE__ */ jsxs(
@@ -339,7 +377,8 @@ function NavList({
339
377
  textTransform: "uppercase",
340
378
  color: MUTED_SOFT,
341
379
  padding: "0 10px 6px",
342
- fontWeight: 600
380
+ fontWeight: 600,
381
+ fontFamily: MONO_FONT
343
382
  },
344
383
  children: section.label
345
384
  }
@@ -615,7 +654,8 @@ function ProfileDropdown({
615
654
  user,
616
655
  onLogout,
617
656
  onNavigate,
618
- dropdownLinks
657
+ dropdownLinks,
658
+ portals
619
659
  }) {
620
660
  const [open, setOpen] = useState(false);
621
661
  const ref = useRef(null);
@@ -689,6 +729,69 @@ function ProfileDropdown({
689
729
  ]
690
730
  }
691
731
  ),
732
+ portals && portals.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
733
+ /* @__PURE__ */ jsx(
734
+ "div",
735
+ {
736
+ style: {
737
+ fontSize: 9.5,
738
+ letterSpacing: "0.12em",
739
+ textTransform: "uppercase",
740
+ color: MUTED_SOFT,
741
+ fontWeight: 600,
742
+ fontFamily: MONO_FONT,
743
+ padding: "8px 12px 2px"
744
+ },
745
+ children: "Switch portal"
746
+ }
747
+ ),
748
+ portals.map(
749
+ (portal) => portal.current ? /* @__PURE__ */ jsxs(
750
+ "div",
751
+ {
752
+ "aria-current": "page",
753
+ style: {
754
+ ...itemStyle,
755
+ color: "var(--brand-color)",
756
+ fontWeight: 600,
757
+ cursor: "default"
758
+ },
759
+ children: [
760
+ /* @__PURE__ */ jsx(
761
+ "span",
762
+ {
763
+ "aria-hidden": true,
764
+ style: {
765
+ width: 14,
766
+ display: "inline-flex",
767
+ justifyContent: "center"
768
+ },
769
+ children: /* @__PURE__ */ jsx(Check, { size: 14 })
770
+ }
771
+ ),
772
+ portal.label
773
+ ]
774
+ },
775
+ portal.href
776
+ ) : /* @__PURE__ */ jsxs(
777
+ Link,
778
+ {
779
+ href: portal.href,
780
+ onClick: () => {
781
+ setOpen(false);
782
+ onNavigate?.();
783
+ },
784
+ style: itemStyle,
785
+ children: [
786
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { width: 14 } }),
787
+ portal.label
788
+ ]
789
+ },
790
+ portal.href
791
+ )
792
+ ),
793
+ /* @__PURE__ */ jsx("div", { style: { borderTop: "1px solid hsl(var(--border, 220 14% 90%))", margin: "4px 0" } })
794
+ ] }),
692
795
  dropdownLinks.map((link) => {
693
796
  const Icon = link.icon;
694
797
  return /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, Check, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalLink,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Optional portal identifier shown as a small uppercase mono\n * subtitle directly below the brand name, e.g. \"Creator\" /\n * \"Affiliator\" for ripllo's sibling portals. Omit on portals that\n * don't need to distinguish themselves (a single-portal product, or\n * ripllo's default merchant surface). */\n brandTag?: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n /** Optional sibling portals — when provided and non-empty, the\n * profile dropdown renders a \"Switch portal\" section listing each\n * one. The entry flagged `current` is shown as the active portal\n * (non-clickable); the rest are links. Omit/empty → no switcher.\n * Ripllo passes all three of its portals on every ripllo portal. */\n portals?: PortalLink[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandTag,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n portals,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n <span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>\n <span\n style={{\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n lineHeight: 1.1,\n }}\n >\n {brandName}\n </span>\n {brandTag && (\n <span\n style={{\n marginTop: 2,\n fontSize: 10,\n fontWeight: 600,\n letterSpacing: '0.1em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n fontFamily: MONO_FONT,\n }}\n >\n {brandTag}\n </span>\n )}\n </span>\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown\n user={user}\n onLogout={onLogout}\n onNavigate={onClose}\n dropdownLinks={dropdownLinks}\n portals={portals}\n />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n/** Monospace stack for label-style chrome (section headings, the\n * brand tag). Pinned so it renders mono on every host regardless of\n * the host page's inherited font. */\nconst MONO_FONT = 'var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n fontFamily: MONO_FONT,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n fontFamily: MONO_FONT,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n portals,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n portals?: PortalLink[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {portals && portals.length > 0 && (\n <>\n <div\n style={{\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n fontWeight: 600,\n fontFamily: MONO_FONT,\n padding: '8px 12px 2px',\n }}\n >\n Switch portal\n </div>\n {portals.map((portal) =>\n portal.current ? (\n <div\n key={portal.href}\n aria-current=\"page\"\n style={{\n ...itemStyle,\n color: 'var(--brand-color)',\n fontWeight: 600,\n cursor: 'default',\n }}\n >\n <span\n aria-hidden\n style={{\n width: 14,\n display: 'inline-flex',\n justifyContent: 'center',\n }}\n >\n <Check size={14} />\n </span>\n {portal.label}\n </div>\n ) : (\n <Link\n key={portal.href}\n href={portal.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <span aria-hidden style={{ width: 14 }} />\n {portal.label}\n </Link>\n ),\n )}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n </>\n )}\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Sidebar } from './Sidebar';\nexport type { SidebarProps } from './Sidebar';\nexport type {\n NavItem,\n NavGroup,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n LucideIcon,\n} from './types';\nexport {\n activeHrefFor,\n titleCase,\n writeActiveWorkspace,\n readActiveWorkspaceId,\n} from './utils';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AAYxB,mBAKO;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Sidebar } from './Sidebar';\nexport type { SidebarProps } from './Sidebar';\nexport type {\n NavItem,\n NavGroup,\n NavModule,\n NavSection,\n PortalLink,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n LucideIcon,\n} from './types';\nexport {\n activeHrefFor,\n titleCase,\n writeActiveWorkspace,\n readActiveWorkspaceId,\n} from './utils';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAAwB;AAaxB,mBAKO;","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Sidebar, SidebarProps } from './Sidebar.cjs';
2
- export { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalWorkspace, SessionUser, WorkspacePersistMode } from './types.cjs';
2
+ export { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalLink, PortalWorkspace, SessionUser, WorkspacePersistMode } from './types.cjs';
3
3
  export { activeHrefFor, readActiveWorkspaceId, titleCase, writeActiveWorkspace } from './utils.cjs';
4
4
  import 'react';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Sidebar, SidebarProps } from './Sidebar.js';
2
- export { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalWorkspace, SessionUser, WorkspacePersistMode } from './types.js';
2
+ export { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalLink, PortalWorkspace, SessionUser, WorkspacePersistMode } from './types.js';
3
3
  export { activeHrefFor, readActiveWorkspaceId, titleCase, writeActiveWorkspace } from './utils.js';
4
4
  import 'react';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Sidebar } from './Sidebar';\nexport type { SidebarProps } from './Sidebar';\nexport type {\n NavItem,\n NavGroup,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n LucideIcon,\n} from './types';\nexport {\n activeHrefFor,\n titleCase,\n writeActiveWorkspace,\n readActiveWorkspaceId,\n} from './utils';\n"],"mappings":"AAAA,SAAS,eAAe;AAYxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Sidebar } from './Sidebar';\nexport type { SidebarProps } from './Sidebar';\nexport type {\n NavItem,\n NavGroup,\n NavModule,\n NavSection,\n PortalLink,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n LucideIcon,\n} from './types';\nexport {\n activeHrefFor,\n titleCase,\n writeActiveWorkspace,\n readActiveWorkspaceId,\n} from './utils';\n"],"mappings":"AAAA,SAAS,eAAe;AAaxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ComponentType, ReactNode, SVGProps } from 'react';\n\nexport type LucideIcon = ComponentType<{\n size?: number;\n strokeWidth?: number;\n style?: React.CSSProperties;\n}>;\n\nexport interface NavItem {\n /** Link target. Omit for an action item — provide `onClick` instead. */\n href?: string;\n /** Action handler. When set, the item renders as a button (e.g. a\n * cart-drawer trigger) rather than a link. */\n onClick?: () => void;\n label: string;\n icon: LucideIcon;\n /** Optional trailing count/indicator pill (e.g. a cart count). */\n badge?: number | string;\n}\n\n/**\n * An optionally-labelled cluster of nav items inside a module. The\n * `label` renders as a small muted sub-heading; omit it for a flat\n * (unheaded) run of items.\n */\nexport interface NavGroup {\n label?: string;\n items: NavItem[];\n}\n\n/**\n * A collapsible accordion within a section. Carries its own icon and\n * label; expanded, it renders either `groups` (sub-headed clusters) or\n * a flat `items` list. Used by module-based portals like storlaunch\n * where the merchant nav is organized by feature module.\n */\nexport interface NavModule {\n label: string;\n icon: LucideIcon;\n items?: NavItem[];\n groups?: NavGroup[];\n}\n\n/**\n * A nav section. Renders flat `items` and/or collapsible `modules`.\n * Both are optional and a section may carry either or both —\n * `items`-only is the backward-compatible two-level form.\n */\nexport interface NavSection {\n label: string;\n items?: NavItem[];\n modules?: NavModule[];\n}\n\nexport interface PortalWorkspace {\n id: string;\n name: string;\n slug?: string;\n plan?: string;\n role?: 'owner' | 'admin' | 'member';\n /** Forjio-internal workspaces get a tiny \"forjio\" badge in the\n * switcher — set on bang's own workspaces, never on customer\n * ones. */\n isForjioInternal?: boolean;\n}\n\nexport interface SessionUser {\n name?: string;\n email?: string;\n}\n\n/**\n * How the active workspace is persisted across page reloads.\n *\n * - `cookie`: writes `<brand>_active_workspace` cookie. Recommended.\n * The backend's auth middleware can read it without a header round-\n * trip. Survives reloads cleanly across subdomains.\n * - `local`: writes `<brand>_active_workspace` to localStorage AND\n * sends `X-Account-Id` header on every request. Legacy pattern\n * from storlaunch/linksnap; works but more fragile.\n * - `api`: POSTs to a `/account/workspaces/<id>/switch` endpoint\n * that mutates server-side session state. Legacy pattern from\n * huudis itself.\n */\nexport type WorkspacePersistMode = 'cookie' | 'local' | 'api';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ComponentType, ReactNode, SVGProps } from 'react';\n\nexport type LucideIcon = ComponentType<{\n size?: number;\n strokeWidth?: number;\n style?: React.CSSProperties;\n}>;\n\nexport interface NavItem {\n /** Link target. Omit for an action item — provide `onClick` instead. */\n href?: string;\n /** Action handler. When set, the item renders as a button (e.g. a\n * cart-drawer trigger) rather than a link. */\n onClick?: () => void;\n label: string;\n icon: LucideIcon;\n /** Optional trailing count/indicator pill (e.g. a cart count). */\n badge?: number | string;\n}\n\n/**\n * An optionally-labelled cluster of nav items inside a module. The\n * `label` renders as a small muted sub-heading; omit it for a flat\n * (unheaded) run of items.\n */\nexport interface NavGroup {\n label?: string;\n items: NavItem[];\n}\n\n/**\n * A collapsible accordion within a section. Carries its own icon and\n * label; expanded, it renders either `groups` (sub-headed clusters) or\n * a flat `items` list. Used by module-based portals like storlaunch\n * where the merchant nav is organized by feature module.\n */\nexport interface NavModule {\n label: string;\n icon: LucideIcon;\n items?: NavItem[];\n groups?: NavGroup[];\n}\n\n/**\n * A nav section. Renders flat `items` and/or collapsible `modules`.\n * Both are optional and a section may carry either or both —\n * `items`-only is the backward-compatible two-level form.\n */\nexport interface NavSection {\n label: string;\n items?: NavItem[];\n modules?: NavModule[];\n}\n\nexport interface PortalWorkspace {\n id: string;\n name: string;\n slug?: string;\n plan?: string;\n role?: 'owner' | 'admin' | 'member';\n /** Forjio-internal workspaces get a tiny \"forjio\" badge in the\n * switcher — set on bang's own workspaces, never on customer\n * ones. */\n isForjioInternal?: boolean;\n}\n\nexport interface SessionUser {\n name?: string;\n email?: string;\n}\n\n/**\n * One entry in the profile dropdown's portal switcher. A Forjio\n * product that ships several sibling portals (e.g. ripllo's creator /\n * affiliator / merchant surfaces) passes the full set on every portal;\n * the one matching the current surface is flagged `current`.\n */\nexport interface PortalLink {\n /** Human label shown in the switcher, e.g. \"Creator\". */\n label: string;\n /** Where selecting this portal navigates. */\n href: string;\n /** Marks the portal the user is currently on — rendered as the\n * active (non-clickable) entry. */\n current?: boolean;\n}\n\n/**\n * How the active workspace is persisted across page reloads.\n *\n * - `cookie`: writes `<brand>_active_workspace` cookie. Recommended.\n * The backend's auth middleware can read it without a header round-\n * trip. Survives reloads cleanly across subdomains.\n * - `local`: writes `<brand>_active_workspace` to localStorage AND\n * sends `X-Account-Id` header on every request. Legacy pattern\n * from storlaunch/linksnap; works but more fragile.\n * - `api`: POSTs to a `/account/workspaces/<id>/switch` endpoint\n * that mutates server-side session state. Legacy pattern from\n * huudis itself.\n */\nexport type WorkspacePersistMode = 'cookie' | 'local' | 'api';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/dist/types.d.cts CHANGED
@@ -62,6 +62,21 @@ interface SessionUser {
62
62
  name?: string;
63
63
  email?: string;
64
64
  }
65
+ /**
66
+ * One entry in the profile dropdown's portal switcher. A Forjio
67
+ * product that ships several sibling portals (e.g. ripllo's creator /
68
+ * affiliator / merchant surfaces) passes the full set on every portal;
69
+ * the one matching the current surface is flagged `current`.
70
+ */
71
+ interface PortalLink {
72
+ /** Human label shown in the switcher, e.g. "Creator". */
73
+ label: string;
74
+ /** Where selecting this portal navigates. */
75
+ href: string;
76
+ /** Marks the portal the user is currently on — rendered as the
77
+ * active (non-clickable) entry. */
78
+ current?: boolean;
79
+ }
65
80
  /**
66
81
  * How the active workspace is persisted across page reloads.
67
82
  *
@@ -77,4 +92,4 @@ interface SessionUser {
77
92
  */
78
93
  type WorkspacePersistMode = 'cookie' | 'local' | 'api';
79
94
 
80
- export type { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalWorkspace, SessionUser, WorkspacePersistMode };
95
+ export type { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalLink, PortalWorkspace, SessionUser, WorkspacePersistMode };
package/dist/types.d.ts CHANGED
@@ -62,6 +62,21 @@ interface SessionUser {
62
62
  name?: string;
63
63
  email?: string;
64
64
  }
65
+ /**
66
+ * One entry in the profile dropdown's portal switcher. A Forjio
67
+ * product that ships several sibling portals (e.g. ripllo's creator /
68
+ * affiliator / merchant surfaces) passes the full set on every portal;
69
+ * the one matching the current surface is flagged `current`.
70
+ */
71
+ interface PortalLink {
72
+ /** Human label shown in the switcher, e.g. "Creator". */
73
+ label: string;
74
+ /** Where selecting this portal navigates. */
75
+ href: string;
76
+ /** Marks the portal the user is currently on — rendered as the
77
+ * active (non-clickable) entry. */
78
+ current?: boolean;
79
+ }
65
80
  /**
66
81
  * How the active workspace is persisted across page reloads.
67
82
  *
@@ -77,4 +92,4 @@ interface SessionUser {
77
92
  */
78
93
  type WorkspacePersistMode = 'cookie' | 'local' | 'api';
79
94
 
80
- export type { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalWorkspace, SessionUser, WorkspacePersistMode };
95
+ export type { LucideIcon, NavGroup, NavItem, NavModule, NavSection, PortalLink, PortalWorkspace, SessionUser, WorkspacePersistMode };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forjio/portal-ui",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Shared portal chrome (sidebar, workspace switcher, profile dropdown, portal shell) for the Forjio family of SaaS products. Mirrors @forjio/website-ui but for the authenticated dashboard side.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,