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