@forjio/portal-ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Sidebar.cjs +72 -24
- package/dist/Sidebar.cjs.map +1 -1
- package/dist/Sidebar.d.cts +5 -1
- package/dist/Sidebar.d.ts +5 -1
- package/dist/Sidebar.js +72 -24
- 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
|
@@ -46,6 +46,7 @@ const DEFAULT_DROPDOWN_LINKS = [
|
|
|
46
46
|
function Sidebar({
|
|
47
47
|
brandSlug,
|
|
48
48
|
brandName,
|
|
49
|
+
brandHref = "/dashboard",
|
|
49
50
|
brandColor,
|
|
50
51
|
brandColorSoft,
|
|
51
52
|
brandIcon,
|
|
@@ -123,9 +124,9 @@ function Sidebar({
|
|
|
123
124
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
124
125
|
import_link.default,
|
|
125
126
|
{
|
|
126
|
-
href:
|
|
127
|
+
href: brandHref,
|
|
127
128
|
onClick: onClose,
|
|
128
|
-
"aria-label": `${brandName}
|
|
129
|
+
"aria-label": `${brandName} home`,
|
|
129
130
|
style: {
|
|
130
131
|
display: "flex",
|
|
131
132
|
alignItems: "center",
|
|
@@ -210,22 +211,71 @@ function subItemLinkStyle(active) {
|
|
|
210
211
|
textDecoration: "none"
|
|
211
212
|
};
|
|
212
213
|
}
|
|
214
|
+
function NavBadge({ value }) {
|
|
215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
216
|
+
"span",
|
|
217
|
+
{
|
|
218
|
+
style: {
|
|
219
|
+
minWidth: 18,
|
|
220
|
+
height: 18,
|
|
221
|
+
padding: "0 5px",
|
|
222
|
+
borderRadius: 9,
|
|
223
|
+
background: "var(--brand-color)",
|
|
224
|
+
color: "#fff",
|
|
225
|
+
fontSize: 10.5,
|
|
226
|
+
fontWeight: 700,
|
|
227
|
+
display: "inline-flex",
|
|
228
|
+
alignItems: "center",
|
|
229
|
+
justifyContent: "center",
|
|
230
|
+
flex: "0 0 auto"
|
|
231
|
+
},
|
|
232
|
+
children: value
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
function NavControl({
|
|
237
|
+
item,
|
|
238
|
+
active,
|
|
239
|
+
style,
|
|
240
|
+
iconSize,
|
|
241
|
+
onNavigate
|
|
242
|
+
}) {
|
|
243
|
+
const Icon = item.icon;
|
|
244
|
+
const inner = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
245
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { size: iconSize, strokeWidth: 2 }),
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label }),
|
|
247
|
+
item.badge != null && item.badge !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavBadge, { value: item.badge })
|
|
248
|
+
] });
|
|
249
|
+
if (item.onClick) {
|
|
250
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
251
|
+
"button",
|
|
252
|
+
{
|
|
253
|
+
type: "button",
|
|
254
|
+
onClick: () => {
|
|
255
|
+
item.onClick?.();
|
|
256
|
+
onNavigate?.();
|
|
257
|
+
},
|
|
258
|
+
style: { ...style, width: "100%", border: "none", textAlign: "left", font: "inherit" },
|
|
259
|
+
children: inner
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
|
|
264
|
+
}
|
|
213
265
|
function NavSubItem({
|
|
214
266
|
item,
|
|
215
267
|
activeHref,
|
|
216
268
|
onNavigate
|
|
217
269
|
}) {
|
|
218
|
-
const
|
|
219
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
220
|
-
|
|
270
|
+
const active = !!item.href && item.href === activeHref;
|
|
271
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
272
|
+
NavControl,
|
|
221
273
|
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
style: subItemLinkStyle(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
|
|
228
|
-
]
|
|
274
|
+
item,
|
|
275
|
+
active,
|
|
276
|
+
style: subItemLinkStyle(active),
|
|
277
|
+
iconSize: 13,
|
|
278
|
+
onNavigate
|
|
229
279
|
}
|
|
230
280
|
) });
|
|
231
281
|
}
|
|
@@ -237,7 +287,7 @@ function NavModuleAccordion({
|
|
|
237
287
|
const descendantHrefs = [
|
|
238
288
|
...(module2.items ?? []).map((i) => i.href),
|
|
239
289
|
...(module2.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
|
|
240
|
-
];
|
|
290
|
+
].filter((h) => typeof h === "string");
|
|
241
291
|
const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
|
|
242
292
|
const [override, setOverride] = (0, import_react.useState)(null);
|
|
243
293
|
const isOpen = override ?? autoOpen;
|
|
@@ -320,19 +370,17 @@ function NavList({
|
|
|
320
370
|
),
|
|
321
371
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
|
|
322
372
|
items.map((item) => {
|
|
323
|
-
const
|
|
324
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
325
|
-
|
|
373
|
+
const active = !!item.href && item.href === activeHref;
|
|
374
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
375
|
+
NavControl,
|
|
326
376
|
{
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
style: itemLinkStyle(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 }, children: item.label })
|
|
333
|
-
]
|
|
377
|
+
item,
|
|
378
|
+
active,
|
|
379
|
+
style: itemLinkStyle(active),
|
|
380
|
+
iconSize: 15,
|
|
381
|
+
onNavigate
|
|
334
382
|
}
|
|
335
|
-
) }, item.href);
|
|
383
|
+
) }, item.href ?? item.label);
|
|
336
384
|
}),
|
|
337
385
|
modules.map((module2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
338
386
|
NavModuleAccordion,
|
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 /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href=\"/dashboard\"\n onClick={onClose}\n aria-label={`${brandName} dashboard`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n return (\n <li>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={subItemLinkStyle(item.href === activeHref)}\n >\n <Icon size={13} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ];\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const Icon = item.icon as LucideIcon;\n return (\n <li key={item.href}>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={itemLinkStyle(item.href === activeHref)}\n >\n <Icon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","module"]}
|
|
1
|
+
{"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{ ...style, width: '100%', border: 'none', textAlign: 'left', font: 'inherit' }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","module"]}
|
package/dist/Sidebar.d.cts
CHANGED
|
@@ -15,6 +15,10 @@ interface SidebarProps {
|
|
|
15
15
|
brandSlug: string;
|
|
16
16
|
/** Display name shown at the top of the sidebar. */
|
|
17
17
|
brandName: string;
|
|
18
|
+
/** Where the brand wordmark / logo links — the portal's home. Default
|
|
19
|
+
* `/dashboard`. No-workspace portals set their own home, e.g.
|
|
20
|
+
* `/creators/dashboard` or a storefront buyer account root. */
|
|
21
|
+
brandHref?: string;
|
|
18
22
|
/** Brand accent color — used for the active-link pill + workspace
|
|
19
23
|
* chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the
|
|
20
24
|
* soft accent is derived by appending an alpha suffix. Forjio family
|
|
@@ -68,6 +72,6 @@ interface SidebarProps {
|
|
|
68
72
|
icon: LucideIcon;
|
|
69
73
|
}[];
|
|
70
74
|
}
|
|
71
|
-
declare function Sidebar({ brandSlug, brandName, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
|
|
75
|
+
declare function Sidebar({ brandSlug, brandName, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
|
|
72
76
|
|
|
73
77
|
export { Sidebar, type SidebarProps };
|
package/dist/Sidebar.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ interface SidebarProps {
|
|
|
15
15
|
brandSlug: string;
|
|
16
16
|
/** Display name shown at the top of the sidebar. */
|
|
17
17
|
brandName: string;
|
|
18
|
+
/** Where the brand wordmark / logo links — the portal's home. Default
|
|
19
|
+
* `/dashboard`. No-workspace portals set their own home, e.g.
|
|
20
|
+
* `/creators/dashboard` or a storefront buyer account root. */
|
|
21
|
+
brandHref?: string;
|
|
18
22
|
/** Brand accent color — used for the active-link pill + workspace
|
|
19
23
|
* chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the
|
|
20
24
|
* soft accent is derived by appending an alpha suffix. Forjio family
|
|
@@ -68,6 +72,6 @@ interface SidebarProps {
|
|
|
68
72
|
icon: LucideIcon;
|
|
69
73
|
}[];
|
|
70
74
|
}
|
|
71
|
-
declare function Sidebar({ brandSlug, brandName, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
|
|
75
|
+
declare function Sidebar({ brandSlug, brandName, brandHref, brandColor, brandColorSoft, brandIcon, workspacePersist, apiSwitchPath, workspaces, activeWorkspaceId, sections, user, onWorkspaceSwitch, onLogout, open, onClose, dropdownLinks, }: SidebarProps): react.JSX.Element;
|
|
72
76
|
|
|
73
77
|
export { Sidebar, type SidebarProps };
|
package/dist/Sidebar.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULT_DROPDOWN_LINKS = [
|
|
|
13
13
|
function Sidebar({
|
|
14
14
|
brandSlug,
|
|
15
15
|
brandName,
|
|
16
|
+
brandHref = "/dashboard",
|
|
16
17
|
brandColor,
|
|
17
18
|
brandColorSoft,
|
|
18
19
|
brandIcon,
|
|
@@ -90,9 +91,9 @@ function Sidebar({
|
|
|
90
91
|
/* @__PURE__ */ jsxs(
|
|
91
92
|
Link,
|
|
92
93
|
{
|
|
93
|
-
href:
|
|
94
|
+
href: brandHref,
|
|
94
95
|
onClick: onClose,
|
|
95
|
-
"aria-label": `${brandName}
|
|
96
|
+
"aria-label": `${brandName} home`,
|
|
96
97
|
style: {
|
|
97
98
|
display: "flex",
|
|
98
99
|
alignItems: "center",
|
|
@@ -177,22 +178,71 @@ function subItemLinkStyle(active) {
|
|
|
177
178
|
textDecoration: "none"
|
|
178
179
|
};
|
|
179
180
|
}
|
|
181
|
+
function NavBadge({ value }) {
|
|
182
|
+
return /* @__PURE__ */ jsx(
|
|
183
|
+
"span",
|
|
184
|
+
{
|
|
185
|
+
style: {
|
|
186
|
+
minWidth: 18,
|
|
187
|
+
height: 18,
|
|
188
|
+
padding: "0 5px",
|
|
189
|
+
borderRadius: 9,
|
|
190
|
+
background: "var(--brand-color)",
|
|
191
|
+
color: "#fff",
|
|
192
|
+
fontSize: 10.5,
|
|
193
|
+
fontWeight: 700,
|
|
194
|
+
display: "inline-flex",
|
|
195
|
+
alignItems: "center",
|
|
196
|
+
justifyContent: "center",
|
|
197
|
+
flex: "0 0 auto"
|
|
198
|
+
},
|
|
199
|
+
children: value
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
function NavControl({
|
|
204
|
+
item,
|
|
205
|
+
active,
|
|
206
|
+
style,
|
|
207
|
+
iconSize,
|
|
208
|
+
onNavigate
|
|
209
|
+
}) {
|
|
210
|
+
const Icon = item.icon;
|
|
211
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
212
|
+
/* @__PURE__ */ jsx(Icon, { size: iconSize, strokeWidth: 2 }),
|
|
213
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label }),
|
|
214
|
+
item.badge != null && item.badge !== "" && /* @__PURE__ */ jsx(NavBadge, { value: item.badge })
|
|
215
|
+
] });
|
|
216
|
+
if (item.onClick) {
|
|
217
|
+
return /* @__PURE__ */ jsx(
|
|
218
|
+
"button",
|
|
219
|
+
{
|
|
220
|
+
type: "button",
|
|
221
|
+
onClick: () => {
|
|
222
|
+
item.onClick?.();
|
|
223
|
+
onNavigate?.();
|
|
224
|
+
},
|
|
225
|
+
style: { ...style, width: "100%", border: "none", textAlign: "left", font: "inherit" },
|
|
226
|
+
children: inner
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return /* @__PURE__ */ jsx(Link, { href: item.href ?? "#", onClick: onNavigate, style, children: inner });
|
|
231
|
+
}
|
|
180
232
|
function NavSubItem({
|
|
181
233
|
item,
|
|
182
234
|
activeHref,
|
|
183
235
|
onNavigate
|
|
184
236
|
}) {
|
|
185
|
-
const
|
|
186
|
-
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */
|
|
187
|
-
|
|
237
|
+
const active = !!item.href && item.href === activeHref;
|
|
238
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
239
|
+
NavControl,
|
|
188
240
|
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
style: subItemLinkStyle(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
|
|
195
|
-
]
|
|
241
|
+
item,
|
|
242
|
+
active,
|
|
243
|
+
style: subItemLinkStyle(active),
|
|
244
|
+
iconSize: 13,
|
|
245
|
+
onNavigate
|
|
196
246
|
}
|
|
197
247
|
) });
|
|
198
248
|
}
|
|
@@ -204,7 +254,7 @@ function NavModuleAccordion({
|
|
|
204
254
|
const descendantHrefs = [
|
|
205
255
|
...(module.items ?? []).map((i) => i.href),
|
|
206
256
|
...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href))
|
|
207
|
-
];
|
|
257
|
+
].filter((h) => typeof h === "string");
|
|
208
258
|
const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);
|
|
209
259
|
const [override, setOverride] = useState(null);
|
|
210
260
|
const isOpen = override ?? autoOpen;
|
|
@@ -287,19 +337,17 @@ function NavList({
|
|
|
287
337
|
),
|
|
288
338
|
/* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: [
|
|
289
339
|
items.map((item) => {
|
|
290
|
-
const
|
|
291
|
-
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */
|
|
292
|
-
|
|
340
|
+
const active = !!item.href && item.href === activeHref;
|
|
341
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
342
|
+
NavControl,
|
|
293
343
|
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
style: itemLinkStyle(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
|
|
300
|
-
]
|
|
344
|
+
item,
|
|
345
|
+
active,
|
|
346
|
+
style: itemLinkStyle(active),
|
|
347
|
+
iconSize: 15,
|
|
348
|
+
onNavigate
|
|
301
349
|
}
|
|
302
|
-
) }, item.href);
|
|
350
|
+
) }, item.href ?? item.label);
|
|
303
351
|
}),
|
|
304
352
|
modules.map((module) => /* @__PURE__ */ jsx(
|
|
305
353
|
NavModuleAccordion,
|
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 /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href=\"/dashboard\"\n onClick={onClose}\n aria-label={`${brandName} dashboard`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n return (\n <li>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={subItemLinkStyle(item.href === activeHref)}\n >\n <Icon size={13} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ];\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const Icon = item.icon as LucideIcon;\n return (\n <li key={item.href}>\n <Link\n href={item.href}\n onClick={onNavigate}\n style={itemLinkStyle(item.href === activeHref)}\n >\n <Icon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n </Link>\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/Sidebar.tsx"],"sourcesContent":["'use client';\n\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { ChevronUp, ChevronDown, ChevronRight, X, LogOut, BookOpen, FileText, Shield } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n LucideIcon,\n NavItem,\n NavModule,\n NavSection,\n PortalWorkspace,\n SessionUser,\n WorkspacePersistMode,\n} from './types';\nimport { activeHrefFor, titleCase, writeActiveWorkspace } from './utils';\n\n/**\n * Forjio family sidebar — workspace switcher on top, nav sections in\n * the middle, profile dropdown at the bottom. Extracted from\n * saas-plugipay 2026-05-19 as the canonical reference.\n *\n * The shell is style-agnostic: every visual is inline CSS driven by\n * CSS custom properties so consumers can theme via a single brandColor\n * prop without depending on Tailwind or any specific token system.\n */\nexport interface SidebarProps {\n /** Slug for cookie/localStorage namespace, e.g. \"plugipay\". */\n brandSlug: string;\n /** Display name shown at the top of the sidebar. */\n brandName: string;\n /** Where the brand wordmark / logo links — the portal's home. Default\n * `/dashboard`. No-workspace portals set their own home, e.g.\n * `/creators/dashboard` or a storefront buyer account root. */\n brandHref?: string;\n /** Brand accent color — used for the active-link pill + workspace\n * chiclet + profile avatar. Must be a 6-digit hex (`#RRGGBB`): the\n * soft accent is derived by appending an alpha suffix. Forjio family\n * default `#1a1a2e`. */\n brandColor: string;\n /** Optional pre-formed \"soft\" accent (active-pill / hover fill).\n * Defaults to `brandColor` at 15% alpha. Pass this when `brandColor`\n * can't be a static hex — e.g. a theme-following `hsl(var(--primary))`\n * value, where the default `${brandColor}26` suffixing would produce\n * invalid CSS. */\n brandColorSoft?: string;\n /** Sidebar logo. Provide a Lucide icon or an `<img>` — anything that\n * renders next to the brand name. */\n brandIcon?: React.ReactNode;\n /** Persistence flavor — see WorkspacePersistMode docs. Required only\n * in workspace mode (i.e. when `workspaces` is passed). */\n workspacePersist?: WorkspacePersistMode;\n /** Only used when workspacePersist='api'. Should contain `{id}` as\n * a placeholder. Example: `/api/v1/account/workspaces/{id}/switch`. */\n apiSwitchPath?: string;\n /** Loaded workspace list — fetched by the host product. **Omit\n * entirely for a no-workspace portal** (a storefront buyer account,\n * or ripllo's creator / affiliator dashboards): the workspace\n * switcher is then not rendered at all — just brand header → nav →\n * profile. */\n workspaces?: PortalWorkspace[];\n /** Active workspace id — host product reads from\n * readActiveWorkspaceId or session state. Unused in no-workspace mode. */\n activeWorkspaceId?: string | null;\n /** Nav sections rendered in order. Most-specific href wins for the\n * active highlight. */\n sections: NavSection[];\n /** Bottom-of-sidebar user info. */\n user: SessionUser | null;\n /** Called when the user picks a different workspace. After the\n * helper writes persistence, the host should refetch its data —\n * the default behavior is to reload the page, but the host can\n * override (e.g. invalidate a SWR cache instead). */\n onWorkspaceSwitch?: (id: string) => void | Promise<void>;\n /** Called when the user clicks Sign out. */\n onLogout: () => void | Promise<void>;\n /** Drawer open state on mobile. */\n open: boolean;\n /** Close handler for the mobile drawer. */\n onClose: () => void;\n /** Optional footer links inside the profile dropdown.\n * Defaults to Docs / Terms / Privacy. */\n dropdownLinks?: { href: string; label: string; icon: LucideIcon }[];\n}\n\nconst DEFAULT_DROPDOWN_LINKS: { href: string; label: string; icon: LucideIcon }[] = [\n { href: '/docs', label: 'Documentation', icon: BookOpen },\n { href: '/terms', label: 'Terms of Service', icon: FileText },\n { href: '/privacy', label: 'Privacy Policy', icon: Shield },\n];\n\nexport function Sidebar({\n brandSlug,\n brandName,\n brandHref = '/dashboard',\n brandColor,\n brandColorSoft,\n brandIcon,\n workspacePersist,\n apiSwitchPath,\n workspaces,\n activeWorkspaceId,\n sections,\n user,\n onWorkspaceSwitch,\n onLogout,\n open,\n onClose,\n dropdownLinks = DEFAULT_DROPDOWN_LINKS,\n}: SidebarProps) {\n const pathname = usePathname() ?? '';\n // Workspace mode is opt-in: a host that omits `workspaces` gets a\n // no-workspace portal — no switcher (buyer / creator / affiliator).\n const workspaceMode = workspaces !== undefined;\n const wsList = workspaces ?? [];\n const active = wsList.find((w) => w.id === activeWorkspaceId) ?? null;\n const others = wsList.filter((w) => w.id !== activeWorkspaceId);\n\n // Theme variables expressed as CSS custom properties; consumers can\n // override on their own root if needed but the props are the canonical\n // surface.\n const themeVars: React.CSSProperties = {\n ['--brand-color' as string]: brandColor,\n ['--brand-soft' as string]: brandColorSoft ?? `${brandColor}26`, // 15% alpha (or caller-supplied)\n };\n\n async function switchWorkspace(id: string) {\n if (!workspacePersist) return; // no-workspace mode — switcher not rendered\n await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);\n if (onWorkspaceSwitch) {\n await onWorkspaceSwitch(id);\n } else if (typeof window !== 'undefined') {\n window.location.reload();\n }\n }\n\n return (\n <>\n {open && (\n <div\n onClick={onClose}\n aria-hidden=\"true\"\n style={{\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.5)',\n zIndex: 40,\n }}\n className=\"lg:hidden\"\n />\n )}\n\n <aside\n style={{\n ...themeVars,\n borderRight: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n color: 'hsl(var(--foreground, 222 47% 11%))',\n width: 248,\n display: 'flex',\n flexDirection: 'column',\n }}\n className={`fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${\n open ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n {/* Brand row */}\n <div\n style={{\n padding: '20px 20px 18px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <Link\n href={brandHref}\n onClick={onClose}\n aria-label={`${brandName} home`}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n fontSize: 18,\n fontWeight: 700,\n letterSpacing: '-0.02em',\n textDecoration: 'none',\n color: 'inherit',\n }}\n >\n {brandIcon}\n {brandName}\n </Link>\n <button\n onClick={onClose}\n className=\"lg:hidden\"\n style={{\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n padding: 4,\n }}\n aria-label=\"Close navigation\"\n >\n <X size={18} />\n </button>\n </div>\n\n {workspaceMode && (\n <WorkspaceSwitcher\n active={active}\n others={others}\n hasAny={wsList.length > 0}\n onSwitch={switchWorkspace}\n onNavigate={onClose}\n />\n )}\n\n <div style={{ flex: 1, padding: '16px 10px', overflowY: 'auto' }}>\n <NavList pathname={pathname} sections={sections} onNavigate={onClose} />\n </div>\n\n <ProfileDropdown user={user} onLogout={onLogout} onNavigate={onClose} dropdownLinks={dropdownLinks} />\n </aside>\n </>\n );\n}\n\nconst FG = 'hsl(var(--foreground, 222 47% 11%))';\nconst MUTED = 'hsl(var(--muted-foreground, 220 9% 46%))';\nconst MUTED_SOFT = 'hsl(var(--muted-foreground, 220 9% 46%) / 0.6)';\n\n/** Style for a top-level nav link (flat item or module toggle). */\nfunction itemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13.5,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '7px 10px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n cursor: 'pointer',\n textDecoration: 'none',\n };\n}\n\n/** Style for an indented sub-item inside an expanded module. */\nfunction subItemLinkStyle(active: boolean): React.CSSProperties {\n return {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n fontSize: 13,\n fontWeight: active ? 600 : 500,\n color: active ? FG : MUTED,\n padding: '6px 10px 6px 32px',\n borderRadius: 8,\n background: active ? 'var(--brand-soft)' : 'transparent',\n textDecoration: 'none',\n };\n}\n\n/** Trailing count/indicator pill (e.g. a cart count). */\nfunction NavBadge({ value }: { value: number | string }) {\n return (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: 'var(--brand-color)',\n color: '#fff',\n fontSize: 10.5,\n fontWeight: 700,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flex: '0 0 auto',\n }}\n >\n {value}\n </span>\n );\n}\n\n/** Renders a nav entry as a link, or — when the item carries an\n * `onClick` — as a button (e.g. a cart-drawer trigger). Optional\n * trailing `badge` pill. Shared by top-level items and module\n * sub-items so both honour action items + badges. */\nfunction NavControl({\n item,\n active,\n style,\n iconSize,\n onNavigate,\n}: {\n item: NavItem;\n active: boolean;\n style: React.CSSProperties;\n iconSize: number;\n onNavigate?: () => void;\n}) {\n const Icon = item.icon as LucideIcon;\n const inner = (\n <>\n <Icon size={iconSize} strokeWidth={2} />\n <span style={{ flex: 1 }}>{item.label}</span>\n {item.badge != null && item.badge !== '' && <NavBadge value={item.badge} />}\n </>\n );\n if (item.onClick) {\n return (\n <button\n type=\"button\"\n onClick={() => {\n item.onClick?.();\n onNavigate?.();\n }}\n style={{ ...style, width: '100%', border: 'none', textAlign: 'left', font: 'inherit' }}\n >\n {inner}\n </button>\n );\n }\n return (\n <Link href={item.href ?? '#'} onClick={onNavigate} style={style}>\n {inner}\n </Link>\n );\n}\n\nfunction NavSubItem({\n item,\n activeHref,\n onNavigate,\n}: {\n item: NavItem;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const active = !!item.href && item.href === activeHref;\n return (\n <li>\n <NavControl\n item={item}\n active={active}\n style={subItemLinkStyle(active)}\n iconSize={13}\n onNavigate={onNavigate}\n />\n </li>\n );\n}\n\n/**\n * A collapsible module accordion: a toggle button (module icon +\n * label + chevron) over an expandable body of `groups` or flat\n * `items`. Auto-opens when a descendant href is the active route;\n * the user can still toggle it shut (or open) afterwards.\n */\nfunction NavModuleAccordion({\n module,\n activeHref,\n onNavigate,\n}: {\n module: NavModule;\n activeHref: string | null;\n onNavigate?: () => void;\n}) {\n const descendantHrefs: string[] = [\n ...(module.items ?? []).map((i) => i.href),\n ...(module.groups ?? []).flatMap((g) => g.items.map((i) => i.href)),\n ].filter((h): h is string => typeof h === 'string');\n const autoOpen = activeHref !== null && descendantHrefs.includes(activeHref);\n // `null` = follow auto-open; once the user clicks, the explicit\n // boolean wins.\n const [override, setOverride] = useState<boolean | null>(null);\n const isOpen = override ?? autoOpen;\n\n const ModIcon = module.icon as LucideIcon;\n const Chevron = isOpen ? ChevronDown : ChevronRight;\n\n const subHeadingStyle: React.CSSProperties = {\n fontSize: 9.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '8px 10px 4px 32px',\n fontWeight: 600,\n };\n\n return (\n <li style={{ display: 'block' }}>\n <button\n type=\"button\"\n onClick={() => setOverride(!isOpen)}\n style={{\n ...itemLinkStyle(autoOpen),\n width: '100%',\n border: 'none',\n textAlign: 'left',\n }}\n aria-expanded={isOpen}\n >\n <ModIcon size={15} strokeWidth={2} />\n <span style={{ flex: 1 }}>{module.label}</span>\n <Chevron size={14} strokeWidth={2} style={{ color: MUTED }} />\n </button>\n {isOpen && (\n <ul style={{ listStyle: 'none', padding: 0, margin: '4px 0 0', display: 'grid', gap: 1 }}>\n {module.groups\n ? module.groups.map((group, gi) => (\n <li key={group.label ?? `__group_${gi}`}>\n {group.label && <div style={subHeadingStyle}>{group.label}</div>}\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {group.items.map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </li>\n ))\n : (module.items ?? []).map((item) => (\n <NavSubItem\n key={item.href}\n item={item}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n )}\n </li>\n );\n}\n\nfunction NavList({\n pathname,\n sections,\n onNavigate,\n}: {\n pathname: string;\n sections: NavSection[];\n onNavigate?: () => void;\n}) {\n const activeHref = activeHrefFor(pathname, sections);\n return (\n <nav aria-label=\"Dashboard\" style={{ display: 'grid', gap: 16 }}>\n {sections.map((section) => {\n const items = section.items ?? [];\n const modules = section.modules ?? [];\n // Skip an empty section so its header doesn't float over nothing.\n if (items.length === 0 && modules.length === 0) return null;\n return (\n <div key={section.label}>\n <div\n style={{\n fontSize: 10.5,\n letterSpacing: '0.12em',\n textTransform: 'uppercase',\n color: MUTED_SOFT,\n padding: '0 10px 6px',\n fontWeight: 600,\n }}\n >\n {section.label}\n </div>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 1 }}>\n {items.map((item) => {\n const active = !!item.href && item.href === activeHref;\n return (\n <li key={item.href ?? item.label}>\n <NavControl\n item={item}\n active={active}\n style={itemLinkStyle(active)}\n iconSize={15}\n onNavigate={onNavigate}\n />\n </li>\n );\n })}\n {modules.map((module) => (\n <NavModuleAccordion\n key={module.label}\n module={module}\n activeHref={activeHref}\n onNavigate={onNavigate}\n />\n ))}\n </ul>\n </div>\n );\n })}\n </nav>\n );\n}\n\nfunction WorkspaceChiclet({ name }: { name: string }) {\n return (\n <span\n aria-hidden\n style={{\n width: 28,\n height: 28,\n flex: '0 0 28px',\n borderRadius: 8,\n background: 'var(--brand-soft)',\n color: 'var(--brand-color)',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n textTransform: 'uppercase',\n border: '1px solid var(--brand-soft)',\n }}\n >\n {name.slice(0, 1)}\n </span>\n );\n}\n\nfunction ForjioBadge() {\n return (\n <span\n title=\"Forjio-operated workspace\"\n style={{\n fontSize: 10,\n textTransform: 'uppercase',\n letterSpacing: '0.06em',\n color: 'var(--brand-color)',\n background: 'var(--brand-soft)',\n border: '1px solid var(--brand-soft)',\n padding: '1px 6px',\n borderRadius: 4,\n flex: '0 0 auto',\n }}\n >\n forjio\n </span>\n );\n}\n\nfunction WorkspaceSwitcher({\n active,\n others,\n hasAny,\n onSwitch,\n onNavigate,\n}: {\n active: PortalWorkspace | null;\n others: PortalWorkspace[];\n hasAny: boolean;\n onSwitch: (id: string) => void;\n onNavigate?: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n if (!hasAny) return null;\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n padding: '12px 10px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n {open && others.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 10,\n right: 10,\n marginTop: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n {others.map((w) => (\n <button\n key={w.id}\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onSwitch(w.id);\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 10px',\n border: 'none',\n background: 'transparent',\n textAlign: 'left',\n cursor: 'pointer',\n borderRadius: 6,\n color: 'inherit',\n }}\n >\n <WorkspaceChiclet name={w.name} />\n <span style={{ flex: 1, minWidth: 0 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {w.name}\n </span>\n {w.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {titleCase(w.role)}\n </span>\n </span>\n </button>\n ))}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <Link\n href=\"/dashboard/workspaces\"\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 10px',\n fontSize: 13,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n textDecoration: 'none',\n borderRadius: 6,\n }}\n >\n + Manage workspaces\n </Link>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n disabled={!active}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '6px 6px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: active ? 'pointer' : 'default',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <WorkspaceChiclet name={active?.name ?? '?'} />\n <span style={{ minWidth: 0, flex: 1 }}>\n <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>\n <span\n style={{\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {active?.name ?? 'Loading…'}\n </span>\n {active?.isForjioInternal && <ForjioBadge />}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n }}\n >\n {active ? titleCase(active.role) : ''}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? 'rotate(180deg)' : '',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n\nfunction ProfileDropdown({\n user,\n onLogout,\n onNavigate,\n dropdownLinks,\n}: {\n user: SessionUser | null;\n onLogout: () => void | Promise<void>;\n onNavigate?: () => void;\n dropdownLinks: { href: string; label: string; icon: LucideIcon }[];\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n function onClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);\n }\n document.addEventListener('mousedown', onClick);\n return () => document.removeEventListener('mousedown', onClick);\n }, []);\n\n const name = user?.name || 'You';\n const email = user?.email || '';\n const initial = (user?.name || user?.email || '?').slice(0, 1).toUpperCase();\n\n const itemStyle: React.CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n padding: '8px 12px',\n fontSize: 13,\n color: 'inherit',\n borderRadius: 6,\n textDecoration: 'none',\n };\n\n return (\n <div\n ref={ref}\n style={{\n position: 'relative',\n borderTop: '1px solid hsl(var(--border, 220 14% 90%))',\n padding: '12px 10px',\n }}\n >\n {open && (\n <div\n style={{\n position: 'absolute',\n bottom: '100%',\n left: 10,\n right: 10,\n marginBottom: 6,\n borderRadius: 10,\n border: '1px solid hsl(var(--border, 220 14% 90%))',\n background: 'hsl(var(--card, 0 0% 100%))',\n boxShadow: '0 10px 30px -12px rgba(0, 0, 0, 0.5)',\n padding: 4,\n zIndex: 20,\n }}\n >\n <div\n style={{\n padding: '10px 12px',\n borderBottom: '1px solid hsl(var(--border, 220 14% 90%))',\n }}\n >\n <div style={{ fontSize: 13, fontWeight: 600 }}>{name}</div>\n <div\n style={{\n fontSize: 12,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n wordBreak: 'break-all',\n }}\n >\n {email}\n </div>\n </div>\n {dropdownLinks.map((link) => {\n const Icon = link.icon;\n return (\n <Link\n key={link.href}\n href={link.href}\n onClick={() => {\n setOpen(false);\n onNavigate?.();\n }}\n style={itemStyle}\n >\n <Icon size={14} /> {link.label}\n </Link>\n );\n })}\n <div style={{ borderTop: '1px solid hsl(var(--border, 220 14% 90%))', margin: '4px 0' }} />\n <button\n type=\"button\"\n onClick={() => {\n setOpen(false);\n onLogout();\n }}\n style={{\n ...itemStyle,\n color: 'hsl(var(--destructive, 0 84% 60%))',\n width: '100%',\n border: 'none',\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n }}\n >\n <LogOut size={14} /> Sign out\n </button>\n </div>\n )}\n\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n width: '100%',\n padding: '8px 8px',\n border: 'none',\n borderRadius: 8,\n background: 'transparent',\n cursor: 'pointer',\n textAlign: 'left',\n color: 'inherit',\n }}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <span\n style={{\n width: 32,\n height: 32,\n flex: '0 0 32px',\n borderRadius: '50%',\n background: 'var(--brand-color)',\n color: '#0b0b10',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n fontSize: 13,\n fontWeight: 700,\n }}\n >\n {initial}\n </span>\n <span style={{ minWidth: 0, flex: 1 }}>\n <span\n style={{\n display: 'block',\n fontSize: 13,\n fontWeight: 600,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {name}\n </span>\n <span\n style={{\n display: 'block',\n fontSize: 11.5,\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {email}\n </span>\n </span>\n <ChevronUp\n size={14}\n strokeWidth={2}\n style={{\n color: 'hsl(var(--muted-foreground, 220 9% 46%))',\n transform: open ? '' : 'rotate(180deg)',\n transition: 'transform 120ms ease',\n }}\n />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
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,
|