@forjio/portal-ui 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Sidebar.cjs CHANGED
@@ -211,22 +211,80 @@ function subItemLinkStyle(active) {
211
211
  textDecoration: "none"
212
212
  };
213
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: {
259
+ ...style,
260
+ width: "100%",
261
+ border: "none",
262
+ textAlign: "left",
263
+ // `fontFamily` only — the `font` shorthand would reset the
264
+ // fontSize/fontWeight that `style` (itemLinkStyle) sets,
265
+ // making the action item bigger than its sibling links.
266
+ fontFamily: "inherit"
267
+ },
268
+ children: inner
269
+ }
270
+ );
271
+ }
272
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
273
+ }
214
274
  function NavSubItem({
215
275
  item,
216
276
  activeHref,
217
277
  onNavigate
218
278
  }) {
219
- const Icon = item.icon;
220
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
221
- import_link.default,
279
+ const active = !!item.href && item.href === activeHref;
280
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
281
+ NavControl,
222
282
  {
223
- href: item.href,
224
- onClick: onNavigate,
225
- style: subItemLinkStyle(item.href === activeHref),
226
- children: [
227
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 13, strokeWidth: 2 }),
228
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
229
- ]
283
+ item,
284
+ active,
285
+ style: subItemLinkStyle(active),
286
+ iconSize: 13,
287
+ onNavigate
230
288
  }
231
289
  ) });
232
290
  }
@@ -238,7 +296,7 @@ function NavModuleAccordion({
238
296
  const descendantHrefs = [
239
297
  ...(module2.items ?? []).map((i) => i.href),
240
298
  ...(module2.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
241
- ];
299
+ ].filter((h) => typeof h === "string");
242
300
  const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
243
301
  const [override, setOverride] = (0, import_react.useState)(null);
244
302
  const isOpen = override ?? autoOpen;
@@ -321,19 +379,17 @@ function NavList({
321
379
  ),
322
380
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
323
381
  items.map((item) => {
324
- const Icon = item.icon;
325
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
326
- import_link.default,
382
+ const active = !!item.href && item.href === activeHref;
383
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
384
+ NavControl,
327
385
  {
328
- href: item.href,
329
- onClick: onNavigate,
330
- style: itemLinkStyle(item.href === activeHref),
331
- children: [
332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: 15, strokeWidth: 2 }),
333
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
334
- ]
386
+ item,
387
+ active,
388
+ style: itemLinkStyle(active),
389
+ iconSize: 15,
390
+ onNavigate
335
391
  }
336
- ) }, item.href);
392
+ ) }, item.href ?? item.label);
337
393
  }),
338
394
  modules.map((module2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
339
395
  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 /** 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\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={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","module"]}
package/dist/Sidebar.js CHANGED
@@ -178,22 +178,80 @@ function subItemLinkStyle(active) {
178
178
  textDecoration: "none"
179
179
  };
180
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: {
226
+ ...style,
227
+ width: "100%",
228
+ border: "none",
229
+ textAlign: "left",
230
+ // `fontFamily` only — the `font` shorthand would reset the
231
+ // fontSize/fontWeight that `style` (itemLinkStyle) sets,
232
+ // making the action item bigger than its sibling links.
233
+ fontFamily: "inherit"
234
+ },
235
+ children: inner
236
+ }
237
+ );
238
+ }
239
+ return /* @__PURE__ */ jsx(Link, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
240
+ }
181
241
  function NavSubItem({
182
242
  item,
183
243
  activeHref,
184
244
  onNavigate
185
245
  }) {
186
- const Icon = item.icon;
187
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
188
- Link,
246
+ const active = !!item.href && item.href === activeHref;
247
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
248
+ NavControl,
189
249
  {
190
- href: item.href,
191
- onClick: onNavigate,
192
- style: subItemLinkStyle(item.href === activeHref),
193
- children: [
194
- /* @__PURE__ */ jsx(Icon, { size: 13, strokeWidth: 2 }),
195
- /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
196
- ]
250
+ item,
251
+ active,
252
+ style: subItemLinkStyle(active),
253
+ iconSize: 13,
254
+ onNavigate
197
255
  }
198
256
  ) });
199
257
  }
@@ -205,7 +263,7 @@ function NavModuleAccordion({
205
263
  const descendantHrefs = [
206
264
  ...(module.items ?? []).map((i) => i.href),
207
265
  ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
208
- ];
266
+ ].filter((h) => typeof h === "string");
209
267
  const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
210
268
  const [override, setOverride] = useState(null);
211
269
  const isOpen = override ?? autoOpen;
@@ -288,19 +346,17 @@ function NavList({
288
346
  ),
289
347
  /* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
290
348
  items.map((item) => {
291
- const Icon = item.icon;
292
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
293
- Link,
349
+ const active = !!item.href && item.href === activeHref;
350
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
351
+ NavControl,
294
352
  {
295
- href: item.href,
296
- onClick: onNavigate,
297
- style: itemLinkStyle(item.href === activeHref),
298
- children: [
299
- /* @__PURE__ */ jsx(Icon, { size: 15, strokeWidth: 2 }),
300
- /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
301
- ]
353
+ item,
354
+ active,
355
+ style: itemLinkStyle(active),
356
+ iconSize: 15,
357
+ onNavigate
302
358
  }
303
- ) }, item.href);
359
+ ) }, item.href ?? item.label);
304
360
  }),
305
361
  modules.map((module) => /* @__PURE__ */ jsx(
306
362
  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 /** 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\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={{\n ...style,\n width: '100%',\n border: 'none',\n textAlign: 'left',\n // `fontFamily` only — the `font` shorthand would reset the\n // fontSize/fontWeight that `style` (itemLinkStyle) sets,\n // making the action item bigger than its sibling links.\n fontFamily: 'inherit',\n }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1 +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.1",
3
+ "version": "0.4.1",
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,