@forjio/portal-ui 0.3.0 → 0.4.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
+ brandHref = "/dashboard",
49
50
  brandColor,
50
51
  brandColorSoft,
51
52
  brandIcon,
@@ -123,9 +124,9 @@ function Sidebar({
123
124
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
124
125
  import_link.default,
125
126
  {
126
- href: "/dashboard",
127
+ href: brandHref,
127
128
  onClick: onClose,
128
- "aria-label": `${brandName} dashboard`,
129
+ "aria-label": `${brandName} home`,
129
130
  style: {
130
131
  display: "flex",
131
132
  alignItems: "center",
@@ -210,22 +211,71 @@ function subItemLinkStyle(active) {
210
211
  textDecoration: "none"
211
212
  };
212
213
  }
214
+ function NavBadge({ value }) {
215
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
216
+ "span",
217
+ {
218
+ style: {
219
+ minWidth: 18,
220
+ height: 18,
221
+ padding: "0 5px",
222
+ borderRadius: 9,
223
+ background: "var(--brand-color)",
224
+ color: "#fff",
225
+ fontSize: 10.5,
226
+ fontWeight: 700,
227
+ display: "inline-flex",
228
+ alignItems: "center",
229
+ justifyContent: "center",
230
+ flex: "0 0 auto"
231
+ },
232
+ children: value
233
+ }
234
+ );
235
+ }
236
+ function NavControl({
237
+ item,
238
+ active,
239
+ style,
240
+ iconSize,
241
+ onNavigate
242
+ }) {
243
+ const Icon = item.icon;
244
+ const inner = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: iconSize, strokeWidth: 2 }),
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label }),
247
+ item.badge != null && item.badge !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavBadge, { value: item.badge })
248
+ ] });
249
+ if (item.onClick) {
250
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
251
+ "button",
252
+ {
253
+ type: "button",
254
+ onClick: () => {
255
+ item.onClick?.();
256
+ onNavigate?.();
257
+ },
258
+ style: { ...style, width: "100%", border: "none", textAlign: "left", font: "inherit" },
259
+ children: inner
260
+ }
261
+ );
262
+ }
263
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
264
+ }
213
265
  function NavSubItem({
214
266
  item,
215
267
  activeHref,
216
268
  onNavigate
217
269
  }) {
218
- const Icon = item.icon;
219
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
220
- import_link.default,
270
+ const active = !!item.href && item.href === activeHref;
271
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
+ NavControl,
221
273
  {
222
- href: item.href,
223
- onClick: onNavigate,
224
- style: subItemLinkStyle(item.href === activeHref),
225
- children: [
226
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 13, strokeWidth: 2 }),
227
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
228
- ]
274
+ item,
275
+ active,
276
+ style: subItemLinkStyle(active),
277
+ iconSize: 13,
278
+ onNavigate
229
279
  }
230
280
  ) });
231
281
  }
@@ -237,7 +287,7 @@ function NavModuleAccordion({
237
287
  const descendantHrefs = [
238
288
  ...(module2.items ?? []).map((i) => i.href),
239
289
  ...(module2.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
240
- ];
290
+ ].filter((h) => typeof h === "string");
241
291
  const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
242
292
  const [override, setOverride] = (0, import_react.useState)(null);
243
293
  const isOpen = override ?? autoOpen;
@@ -320,19 +370,17 @@ function NavList({
320
370
  ),
321
371
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
322
372
  items.map((item) => {
323
- const Icon = item.icon;
324
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
325
- import_link.default,
373
+ const active = !!item.href && item.href === activeHref;
374
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
375
+ NavControl,
326
376
  {
327
- href: item.href,
328
- onClick: onNavigate,
329
- style: itemLinkStyle(item.href === activeHref),
330
- children: [
331
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 15, strokeWidth: 2 }),
332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
333
- ]
377
+ item,
378
+ active,
379
+ style: itemLinkStyle(active),
380
+ iconSize: 15,
381
+ onNavigate
334
382
  }
335
- ) }, item.href);
383
+ ) }, item.href ?? item.label);
336
384
  }),
337
385
  modules.map((module2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
338
386
  NavModuleAccordion,
@@ -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 /** 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 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=\"/dashboard\"\n onClick={onClose}\n aria-label={`${brandName} dashboard`}\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\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n return (\n <li>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={subItemLinkStyle(item.href === activeHref)}\n >\n <Icon size={13} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\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 ];\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 Icon = item.icon as LucideIcon;\n return (\n <li key={item.href}>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={itemLinkStyle(item.href === activeHref)}\n >\n <Icon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\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, 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={{ ...style, width: '100%', border: 'none', textAlign: 'left', font: 'inherit' }}\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"]}
@@ -15,6 +15,10 @@ interface SidebarProps {
15
15
  brandSlug: string;
16
16
  /** Display name shown at the top of the sidebar. */
17
17
  brandName: string;
18
+ /** Where the brand wordmark / logo links — the portal's home. Default
19
+ * `/dashboard`. No-workspace portals set their own home, e.g.
20
+ * `/creators/dashboard` or a storefront buyer account root. */
21
+ brandHref?: string;
18
22
  /** Brand accent color — used for the active-link pill + workspace
19
23
  * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the
20
24
  * soft accent is derived by appending an alpha suffix. Forjio family
@@ -68,6 +72,6 @@ interface SidebarProps {
68
72
  icon: LucideIcon;
69
73
  }[];
70
74
  }
71
- declare function Sidebar({ brandSlug, brandName, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
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;
72
76
 
73
77
  export { Sidebar, type SidebarProps };
package/dist/Sidebar.d.ts CHANGED
@@ -15,6 +15,10 @@ interface SidebarProps {
15
15
  brandSlug: string;
16
16
  /** Display name shown at the top of the sidebar. */
17
17
  brandName: string;
18
+ /** Where the brand wordmark / logo links — the portal's home. Default
19
+ * `/dashboard`. No-workspace portals set their own home, e.g.
20
+ * `/creators/dashboard` or a storefront buyer account root. */
21
+ brandHref?: string;
18
22
  /** Brand accent color — used for the active-link pill + workspace
19
23
  * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the
20
24
  * soft accent is derived by appending an alpha suffix. Forjio family
@@ -68,6 +72,6 @@ interface SidebarProps {
68
72
  icon: LucideIcon;
69
73
  }[];
70
74
  }
71
- declare function Sidebar({ brandSlug, brandName, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
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;
72
76
 
73
77
  export { Sidebar, type SidebarProps };
package/dist/Sidebar.js CHANGED
@@ -13,6 +13,7 @@ const DEFAULT_DROPDOWN_LINKS = [
13
13
  function Sidebar({
14
14
  brandSlug,
15
15
  brandName,
16
+ brandHref = "/dashboard",
16
17
  brandColor,
17
18
  brandColorSoft,
18
19
  brandIcon,
@@ -90,9 +91,9 @@ function Sidebar({
90
91
  /* @__PURE__ */ jsxs(
91
92
  Link,
92
93
  {
93
- href: "/dashboard",
94
+ href: brandHref,
94
95
  onClick: onClose,
95
- "aria-label": `${brandName} dashboard`,
96
+ "aria-label": `${brandName} home`,
96
97
  style: {
97
98
  display: "flex",
98
99
  alignItems: "center",
@@ -177,22 +178,71 @@ function subItemLinkStyle(active) {
177
178
  textDecoration: "none"
178
179
  };
179
180
  }
181
+ function NavBadge({ value }) {
182
+ return /* @__PURE__ */ jsx(
183
+ "span",
184
+ {
185
+ style: {
186
+ minWidth: 18,
187
+ height: 18,
188
+ padding: "0 5px",
189
+ borderRadius: 9,
190
+ background: "var(--brand-color)",
191
+ color: "#fff",
192
+ fontSize: 10.5,
193
+ fontWeight: 700,
194
+ display: "inline-flex",
195
+ alignItems: "center",
196
+ justifyContent: "center",
197
+ flex: "0 0 auto"
198
+ },
199
+ children: value
200
+ }
201
+ );
202
+ }
203
+ function NavControl({
204
+ item,
205
+ active,
206
+ style,
207
+ iconSize,
208
+ onNavigate
209
+ }) {
210
+ const Icon = item.icon;
211
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
212
+ /* @__PURE__ */ jsx(Icon, { size: iconSize, strokeWidth: 2 }),
213
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label }),
214
+ item.badge != null && item.badge !== "" && /* @__PURE__ */ jsx(NavBadge, { value: item.badge })
215
+ ] });
216
+ if (item.onClick) {
217
+ return /* @__PURE__ */ jsx(
218
+ "button",
219
+ {
220
+ type: "button",
221
+ onClick: () => {
222
+ item.onClick?.();
223
+ onNavigate?.();
224
+ },
225
+ style: { ...style, width: "100%", border: "none", textAlign: "left", font: "inherit" },
226
+ children: inner
227
+ }
228
+ );
229
+ }
230
+ return /* @__PURE__ */ jsx(Link, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
231
+ }
180
232
  function NavSubItem({
181
233
  item,
182
234
  activeHref,
183
235
  onNavigate
184
236
  }) {
185
- const Icon = item.icon;
186
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
187
- Link,
237
+ const active = !!item.href && item.href === activeHref;
238
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
239
+ NavControl,
188
240
  {
189
- href: item.href,
190
- onClick: onNavigate,
191
- style: subItemLinkStyle(item.href === activeHref),
192
- children: [
193
- /* @__PURE__ */ jsx(Icon, { size: 13, strokeWidth: 2 }),
194
- /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
195
- ]
241
+ item,
242
+ active,
243
+ style: subItemLinkStyle(active),
244
+ iconSize: 13,
245
+ onNavigate
196
246
  }
197
247
  ) });
198
248
  }
@@ -204,7 +254,7 @@ function NavModuleAccordion({
204
254
  const descendantHrefs = [
205
255
  ...(module.items ?? []).map((i) => i.href),
206
256
  ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
207
- ];
257
+ ].filter((h) => typeof h === "string");
208
258
  const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
209
259
  const [override, setOverride] = useState(null);
210
260
  const isOpen = override ?? autoOpen;
@@ -287,19 +337,17 @@ function NavList({
287
337
  ),
288
338
  /* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
289
339
  items.map((item) => {
290
- const Icon = item.icon;
291
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
292
- Link,
340
+ const active = !!item.href && item.href === activeHref;
341
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
342
+ NavControl,
293
343
  {
294
- href: item.href,
295
- onClick: onNavigate,
296
- style: itemLinkStyle(item.href === activeHref),
297
- children: [
298
- /* @__PURE__ */ jsx(Icon, { size: 15, strokeWidth: 2 }),
299
- /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
300
- ]
344
+ item,
345
+ active,
346
+ style: itemLinkStyle(active),
347
+ iconSize: 15,
348
+ onNavigate
301
349
  }
302
- ) }, item.href);
350
+ ) }, item.href ?? item.label);
303
351
  }),
304
352
  modules.map((module) => /* @__PURE__ */ jsx(
305
353
  NavModuleAccordion,
@@ -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 /** 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 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=\"/dashboard\"\n onClick={onClose}\n aria-label={`${brandName} dashboard`}\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\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n return (\n <li>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={subItemLinkStyle(item.href === activeHref)}\n >\n <Icon size={13} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\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 ];\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 Icon = item.icon as LucideIcon;\n return (\n <li key={item.href}>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={itemLinkStyle(item.href === activeHref)}\n >\n <Icon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\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, 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={{ ...style, width: '100%', border: 'none', textAlign: 'left', font: 'inherit' }}\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 +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 href: string;\n label: string;\n icon: LucideIcon;\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 * 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
@@ -6,9 +6,15 @@ type LucideIcon = ComponentType<{
6
6
  style?: React.CSSProperties;
7
7
  }>;
8
8
  interface NavItem {
9
- href: string;
9
+ /** Link target. Omit for an action item — provide `onClick` instead. */
10
+ href?: string;
11
+ /** Action handler. When set, the item renders as a button (e.g. a
12
+ * cart-drawer trigger) rather than a link. */
13
+ onClick?: () => void;
10
14
  label: string;
11
15
  icon: LucideIcon;
16
+ /** Optional trailing count/indicator pill (e.g. a cart count). */
17
+ badge?: number | string;
12
18
  }
13
19
  /**
14
20
  * An optionally-labelled cluster of nav items inside a module. The
package/dist/types.d.ts CHANGED
@@ -6,9 +6,15 @@ type LucideIcon = ComponentType<{
6
6
  style?: React.CSSProperties;
7
7
  }>;
8
8
  interface NavItem {
9
- href: string;
9
+ /** Link target. Omit for an action item — provide `onClick` instead. */
10
+ href?: string;
11
+ /** Action handler. When set, the item renders as a button (e.g. a
12
+ * cart-drawer trigger) rather than a link. */
13
+ onClick?: () => void;
10
14
  label: string;
11
15
  icon: LucideIcon;
16
+ /** Optional trailing count/indicator pill (e.g. a cart count). */
17
+ badge?: number | string;
12
18
  }
13
19
  /**
14
20
  * An optionally-labelled cluster of nav items inside a module. The
package/dist/utils.cjs CHANGED
@@ -31,7 +31,7 @@ function activeHrefFor(pathname, sections) {
31
31
  ...(m.items ?? []).map((i) => i.href),
32
32
  ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
33
33
  ])
34
- ]);
34
+ ]).filter((h) => typeof h === "string");
35
35
  let best = null;
36
36
  for (const href of candidates) {
37
37
  const matches = href === "/dashboard" ? pathname === "/dashboard" : pathname === href || pathname.startsWith(href + "/");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import type { NavSection } from './types';\n\n/** Find the longest-prefix-matching href in `sections` for the\n * current pathname. Used by Sidebar to highlight the active item.\n * Walks both flat `items` and any collapsible `modules` (incl. their\n * groups), so module-based portals get correct highlighting. */\nexport function activeHrefFor(pathname: string, sections: NavSection[]): string | null {\n const candidates = sections.flatMap((s) => [\n ...(s.items ?? []).map((i) => i.href),\n ...(s.modules ?? []).flatMap((m) => [\n ...(m.items ?? []).map((i) => i.href),\n ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ]),\n ]);\n let best: string | null = null;\n for (const href of candidates) {\n const matches =\n href === '/dashboard'\n ? pathname === '/dashboard'\n : pathname === href || pathname.startsWith(href + '/');\n if (matches && (best === null || href.length > best.length)) best = href;\n }\n return best;\n}\n\nexport function titleCase(s: string | null | undefined): string {\n if (!s) return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\n/**\n * Persist + reload pattern shared across the three storage modes.\n * Sidebar passes whichever flavor the consuming product uses; this\n * util encapsulates the cookie/localStorage/API call.\n */\nexport function writeActiveWorkspace(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n workspaceId: string,\n apiSwitchPath?: string,\n): void | Promise<void> {\n if (typeof window === 'undefined') return;\n if (mode === 'cookie') {\n const secure = location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${brandSlug}_active_workspace=${encodeURIComponent(\n workspaceId,\n )}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax${secure}`;\n return;\n }\n if (mode === 'local') {\n localStorage.setItem(`${brandSlug}_active_workspace`, workspaceId);\n return;\n }\n if (mode === 'api' && apiSwitchPath) {\n return fetch(apiSwitchPath.replace('{id}', encodeURIComponent(workspaceId)), {\n method: 'POST',\n credentials: 'include',\n }).then(() => undefined);\n }\n}\n\n/** Read whichever persistence the consumer uses. Returns null on SSR\n * (no window). For `api` mode the caller should pull from session\n * themselves — there's no stable client-side cache. */\nexport function readActiveWorkspaceId(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n): string | null {\n if (typeof window === 'undefined') return null;\n if (mode === 'cookie') {\n const match = document.cookie\n .split('; ')\n .find((r) => r.startsWith(`${brandSlug}_active_workspace=`));\n if (!match) return null;\n return decodeURIComponent(match.split('=').slice(1).join('='));\n }\n if (mode === 'local') {\n return localStorage.getItem(`${brandSlug}_active_workspace`);\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,cAAc,UAAkB,UAAuC;AACrF,QAAM,aAAa,SAAS,QAAQ,CAAC,MAAM;AAAA,IACzC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACpC,IAAI,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,MAAM;AAAA,MAClC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACpC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACD,MAAI,OAAsB;AAC1B,aAAW,QAAQ,YAAY;AAC7B,UAAM,UACJ,SAAS,eACL,aAAa,eACb,aAAa,QAAQ,SAAS,WAAW,OAAO,GAAG;AACzD,QAAI,YAAY,SAAS,QAAQ,KAAK,SAAS,KAAK,QAAS,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAEO,SAAS,UAAU,GAAsC;AAC9D,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAC5D;AAOO,SAAS,qBACd,MACA,WACA,aACA,eACsB;AACtB,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,SAAS,aAAa,WAAW,aAAa;AAC7D,aAAS,SAAS,GAAG,SAAS,qBAAqB;AAAA,MACjD;AAAA,IACF,CAAC,qBAAqB,KAAK,KAAK,KAAK,EAAE,iBAAiB,MAAM;AAC9D;AAAA,EACF;AACA,MAAI,SAAS,SAAS;AACpB,iBAAa,QAAQ,GAAG,SAAS,qBAAqB,WAAW;AACjE;AAAA,EACF;AACA,MAAI,SAAS,SAAS,eAAe;AACnC,WAAO,MAAM,cAAc,QAAQ,QAAQ,mBAAmB,WAAW,CAAC,GAAG;AAAA,MAC3E,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,CAAC,EAAE,KAAK,MAAM,MAAS;AAAA,EACzB;AACF;AAKO,SAAS,sBACd,MACA,WACe;AACf,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,SAAS,UAAU;AACrB,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,oBAAoB,CAAC;AAC7D,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,mBAAmB,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC/D;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,aAAa,QAAQ,GAAG,SAAS,mBAAmB;AAAA,EAC7D;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import type { NavSection } from './types';\n\n/** Find the longest-prefix-matching href in `sections` for the\n * current pathname. Used by Sidebar to highlight the active item.\n * Walks both flat `items` and any collapsible `modules` (incl. their\n * groups), so module-based portals get correct highlighting. */\nexport function activeHrefFor(pathname: string, sections: NavSection[]): string | null {\n const candidates = sections\n .flatMap((s) => [\n ...(s.items ?? []).map((i) => i.href),\n ...(s.modules ?? []).flatMap((m) => [\n ...(m.items ?? []).map((i) => i.href),\n ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ]),\n ])\n // action items carry no href — drop them before prefix-matching\n .filter((h): h is string => typeof h === 'string');\n let best: string | null = null;\n for (const href of candidates) {\n const matches =\n href === '/dashboard'\n ? pathname === '/dashboard'\n : pathname === href || pathname.startsWith(href + '/');\n if (matches && (best === null || href.length > best.length)) best = href;\n }\n return best;\n}\n\nexport function titleCase(s: string | null | undefined): string {\n if (!s) return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\n/**\n * Persist + reload pattern shared across the three storage modes.\n * Sidebar passes whichever flavor the consuming product uses; this\n * util encapsulates the cookie/localStorage/API call.\n */\nexport function writeActiveWorkspace(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n workspaceId: string,\n apiSwitchPath?: string,\n): void | Promise<void> {\n if (typeof window === 'undefined') return;\n if (mode === 'cookie') {\n const secure = location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${brandSlug}_active_workspace=${encodeURIComponent(\n workspaceId,\n )}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax${secure}`;\n return;\n }\n if (mode === 'local') {\n localStorage.setItem(`${brandSlug}_active_workspace`, workspaceId);\n return;\n }\n if (mode === 'api' && apiSwitchPath) {\n return fetch(apiSwitchPath.replace('{id}', encodeURIComponent(workspaceId)), {\n method: 'POST',\n credentials: 'include',\n }).then(() => undefined);\n }\n}\n\n/** Read whichever persistence the consumer uses. Returns null on SSR\n * (no window). For `api` mode the caller should pull from session\n * themselves — there's no stable client-side cache. */\nexport function readActiveWorkspaceId(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n): string | null {\n if (typeof window === 'undefined') return null;\n if (mode === 'cookie') {\n const match = document.cookie\n .split('; ')\n .find((r) => r.startsWith(`${brandSlug}_active_workspace=`));\n if (!match) return null;\n return decodeURIComponent(match.split('=').slice(1).join('='));\n }\n if (mode === 'local') {\n return localStorage.getItem(`${brandSlug}_active_workspace`);\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,cAAc,UAAkB,UAAuC;AACrF,QAAM,aAAa,SAChB,QAAQ,CAAC,MAAM;AAAA,IACd,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACpC,IAAI,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,MAAM;AAAA,MAClC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACpC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC,EAEA,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AACnD,MAAI,OAAsB;AAC1B,aAAW,QAAQ,YAAY;AAC7B,UAAM,UACJ,SAAS,eACL,aAAa,eACb,aAAa,QAAQ,SAAS,WAAW,OAAO,GAAG;AACzD,QAAI,YAAY,SAAS,QAAQ,KAAK,SAAS,KAAK,QAAS,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAEO,SAAS,UAAU,GAAsC;AAC9D,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAC5D;AAOO,SAAS,qBACd,MACA,WACA,aACA,eACsB;AACtB,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,SAAS,aAAa,WAAW,aAAa;AAC7D,aAAS,SAAS,GAAG,SAAS,qBAAqB;AAAA,MACjD;AAAA,IACF,CAAC,qBAAqB,KAAK,KAAK,KAAK,EAAE,iBAAiB,MAAM;AAC9D;AAAA,EACF;AACA,MAAI,SAAS,SAAS;AACpB,iBAAa,QAAQ,GAAG,SAAS,qBAAqB,WAAW;AACjE;AAAA,EACF;AACA,MAAI,SAAS,SAAS,eAAe;AACnC,WAAO,MAAM,cAAc,QAAQ,QAAQ,mBAAmB,WAAW,CAAC,GAAG;AAAA,MAC3E,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,CAAC,EAAE,KAAK,MAAM,MAAS;AAAA,EACzB;AACF;AAKO,SAAS,sBACd,MACA,WACe;AACf,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,SAAS,UAAU;AACrB,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,oBAAoB,CAAC;AAC7D,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,mBAAmB,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC/D;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,aAAa,QAAQ,GAAG,SAAS,mBAAmB;AAAA,EAC7D;AACA,SAAO;AACT;","names":[]}
package/dist/utils.js CHANGED
@@ -5,7 +5,7 @@ function activeHrefFor(pathname, sections) {
5
5
  ...(m.items ?? []).map((i) => i.href),
6
6
  ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
7
7
  ])
8
- ]);
8
+ ]).filter((h) => typeof h === "string");
9
9
  let best = null;
10
10
  for (const href of candidates) {
11
11
  const matches = href === "/dashboard" ? pathname === "/dashboard" : pathname === href || pathname.startsWith(href + "/");
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import type { NavSection } from './types';\n\n/** Find the longest-prefix-matching href in `sections` for the\n * current pathname. Used by Sidebar to highlight the active item.\n * Walks both flat `items` and any collapsible `modules` (incl. their\n * groups), so module-based portals get correct highlighting. */\nexport function activeHrefFor(pathname: string, sections: NavSection[]): string | null {\n const candidates = sections.flatMap((s) => [\n ...(s.items ?? []).map((i) => i.href),\n ...(s.modules ?? []).flatMap((m) => [\n ...(m.items ?? []).map((i) => i.href),\n ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ]),\n ]);\n let best: string | null = null;\n for (const href of candidates) {\n const matches =\n href === '/dashboard'\n ? pathname === '/dashboard'\n : pathname === href || pathname.startsWith(href + '/');\n if (matches && (best === null || href.length > best.length)) best = href;\n }\n return best;\n}\n\nexport function titleCase(s: string | null | undefined): string {\n if (!s) return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\n/**\n * Persist + reload pattern shared across the three storage modes.\n * Sidebar passes whichever flavor the consuming product uses; this\n * util encapsulates the cookie/localStorage/API call.\n */\nexport function writeActiveWorkspace(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n workspaceId: string,\n apiSwitchPath?: string,\n): void | Promise<void> {\n if (typeof window === 'undefined') return;\n if (mode === 'cookie') {\n const secure = location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${brandSlug}_active_workspace=${encodeURIComponent(\n workspaceId,\n )}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax${secure}`;\n return;\n }\n if (mode === 'local') {\n localStorage.setItem(`${brandSlug}_active_workspace`, workspaceId);\n return;\n }\n if (mode === 'api' && apiSwitchPath) {\n return fetch(apiSwitchPath.replace('{id}', encodeURIComponent(workspaceId)), {\n method: 'POST',\n credentials: 'include',\n }).then(() => undefined);\n }\n}\n\n/** Read whichever persistence the consumer uses. Returns null on SSR\n * (no window). For `api` mode the caller should pull from session\n * themselves — there's no stable client-side cache. */\nexport function readActiveWorkspaceId(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n): string | null {\n if (typeof window === 'undefined') return null;\n if (mode === 'cookie') {\n const match = document.cookie\n .split('; ')\n .find((r) => r.startsWith(`${brandSlug}_active_workspace=`));\n if (!match) return null;\n return decodeURIComponent(match.split('=').slice(1).join('='));\n }\n if (mode === 'local') {\n return localStorage.getItem(`${brandSlug}_active_workspace`);\n }\n return null;\n}\n"],"mappings":"AAMO,SAAS,cAAc,UAAkB,UAAuC;AACrF,QAAM,aAAa,SAAS,QAAQ,CAAC,MAAM;AAAA,IACzC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACpC,IAAI,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,MAAM;AAAA,MAClC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACpC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC;AACD,MAAI,OAAsB;AAC1B,aAAW,QAAQ,YAAY;AAC7B,UAAM,UACJ,SAAS,eACL,aAAa,eACb,aAAa,QAAQ,SAAS,WAAW,OAAO,GAAG;AACzD,QAAI,YAAY,SAAS,QAAQ,KAAK,SAAS,KAAK,QAAS,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAEO,SAAS,UAAU,GAAsC;AAC9D,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAC5D;AAOO,SAAS,qBACd,MACA,WACA,aACA,eACsB;AACtB,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,SAAS,aAAa,WAAW,aAAa;AAC7D,aAAS,SAAS,GAAG,SAAS,qBAAqB;AAAA,MACjD;AAAA,IACF,CAAC,qBAAqB,KAAK,KAAK,KAAK,EAAE,iBAAiB,MAAM;AAC9D;AAAA,EACF;AACA,MAAI,SAAS,SAAS;AACpB,iBAAa,QAAQ,GAAG,SAAS,qBAAqB,WAAW;AACjE;AAAA,EACF;AACA,MAAI,SAAS,SAAS,eAAe;AACnC,WAAO,MAAM,cAAc,QAAQ,QAAQ,mBAAmB,WAAW,CAAC,GAAG;AAAA,MAC3E,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,CAAC,EAAE,KAAK,MAAM,MAAS;AAAA,EACzB;AACF;AAKO,SAAS,sBACd,MACA,WACe;AACf,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,SAAS,UAAU;AACrB,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,oBAAoB,CAAC;AAC7D,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,mBAAmB,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC/D;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,aAAa,QAAQ,GAAG,SAAS,mBAAmB;AAAA,EAC7D;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import type { NavSection } from './types';\n\n/** Find the longest-prefix-matching href in `sections` for the\n * current pathname. Used by Sidebar to highlight the active item.\n * Walks both flat `items` and any collapsible `modules` (incl. their\n * groups), so module-based portals get correct highlighting. */\nexport function activeHrefFor(pathname: string, sections: NavSection[]): string | null {\n const candidates = sections\n .flatMap((s) => [\n ...(s.items ?? []).map((i) => i.href),\n ...(s.modules ?? []).flatMap((m) => [\n ...(m.items ?? []).map((i) => i.href),\n ...(m.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ]),\n ])\n // action items carry no href — drop them before prefix-matching\n .filter((h): h is string => typeof h === 'string');\n let best: string | null = null;\n for (const href of candidates) {\n const matches =\n href === '/dashboard'\n ? pathname === '/dashboard'\n : pathname === href || pathname.startsWith(href + '/');\n if (matches && (best === null || href.length > best.length)) best = href;\n }\n return best;\n}\n\nexport function titleCase(s: string | null | undefined): string {\n if (!s) return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\n/**\n * Persist + reload pattern shared across the three storage modes.\n * Sidebar passes whichever flavor the consuming product uses; this\n * util encapsulates the cookie/localStorage/API call.\n */\nexport function writeActiveWorkspace(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n workspaceId: string,\n apiSwitchPath?: string,\n): void | Promise<void> {\n if (typeof window === 'undefined') return;\n if (mode === 'cookie') {\n const secure = location.protocol === 'https:' ? '; Secure' : '';\n document.cookie = `${brandSlug}_active_workspace=${encodeURIComponent(\n workspaceId,\n )}; path=/; max-age=${30 * 24 * 60 * 60}; SameSite=Lax${secure}`;\n return;\n }\n if (mode === 'local') {\n localStorage.setItem(`${brandSlug}_active_workspace`, workspaceId);\n return;\n }\n if (mode === 'api' && apiSwitchPath) {\n return fetch(apiSwitchPath.replace('{id}', encodeURIComponent(workspaceId)), {\n method: 'POST',\n credentials: 'include',\n }).then(() => undefined);\n }\n}\n\n/** Read whichever persistence the consumer uses. Returns null on SSR\n * (no window). For `api` mode the caller should pull from session\n * themselves — there's no stable client-side cache. */\nexport function readActiveWorkspaceId(\n mode: 'cookie' | 'local' | 'api',\n brandSlug: string,\n): string | null {\n if (typeof window === 'undefined') return null;\n if (mode === 'cookie') {\n const match = document.cookie\n .split('; ')\n .find((r) => r.startsWith(`${brandSlug}_active_workspace=`));\n if (!match) return null;\n return decodeURIComponent(match.split('=').slice(1).join('='));\n }\n if (mode === 'local') {\n return localStorage.getItem(`${brandSlug}_active_workspace`);\n }\n return null;\n}\n"],"mappings":"AAMO,SAAS,cAAc,UAAkB,UAAuC;AACrF,QAAM,aAAa,SAChB,QAAQ,CAAC,MAAM;AAAA,IACd,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACpC,IAAI,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,MAAM;AAAA,MAClC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACpC,IAAI,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH,CAAC,EAEA,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AACnD,MAAI,OAAsB;AAC1B,aAAW,QAAQ,YAAY;AAC7B,UAAM,UACJ,SAAS,eACL,aAAa,eACb,aAAa,QAAQ,SAAS,WAAW,OAAO,GAAG;AACzD,QAAI,YAAY,SAAS,QAAQ,KAAK,SAAS,KAAK,QAAS,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAEO,SAAS,UAAU,GAAsC;AAC9D,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAC5D;AAOO,SAAS,qBACd,MACA,WACA,aACA,eACsB;AACtB,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,SAAS,UAAU;AACrB,UAAM,SAAS,SAAS,aAAa,WAAW,aAAa;AAC7D,aAAS,SAAS,GAAG,SAAS,qBAAqB;AAAA,MACjD;AAAA,IACF,CAAC,qBAAqB,KAAK,KAAK,KAAK,EAAE,iBAAiB,MAAM;AAC9D;AAAA,EACF;AACA,MAAI,SAAS,SAAS;AACpB,iBAAa,QAAQ,GAAG,SAAS,qBAAqB,WAAW;AACjE;AAAA,EACF;AACA,MAAI,SAAS,SAAS,eAAe;AACnC,WAAO,MAAM,cAAc,QAAQ,QAAQ,mBAAmB,WAAW,CAAC,GAAG;AAAA,MAC3E,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,CAAC,EAAE,KAAK,MAAM,MAAS;AAAA,EACzB;AACF;AAKO,SAAS,sBACd,MACA,WACe;AACf,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,SAAS,UAAU;AACrB,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,oBAAoB,CAAC;AAC7D,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,mBAAmB,MAAM,MAAM,GAAG,EAAE,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC/D;AACA,MAAI,SAAS,SAAS;AACpB,WAAO,aAAa,QAAQ,GAAG,SAAS,mBAAmB;AAAA,EAC7D;AACA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forjio/portal-ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.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,