@forjio/portal-ui 0.1.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.
@@ -0,0 +1,645 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { ChevronUp, X, LogOut, BookOpen, FileText, Shield } from "lucide-react";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { activeHrefFor, titleCase, writeActiveWorkspace } from "./utils";
8
+ const DEFAULT_DROPDOWN_LINKS = [
9
+ { href: "/docs", label: "Documentation", icon: BookOpen },
10
+ { href: "/terms", label: "Terms of Service", icon: FileText },
11
+ { href: "/privacy", label: "Privacy Policy", icon: Shield }
12
+ ];
13
+ function Sidebar({
14
+ brandSlug,
15
+ brandName,
16
+ brandColor,
17
+ brandIcon,
18
+ workspacePersist,
19
+ apiSwitchPath,
20
+ workspaces,
21
+ activeWorkspaceId,
22
+ sections,
23
+ user,
24
+ onWorkspaceSwitch,
25
+ onLogout,
26
+ open,
27
+ onClose,
28
+ dropdownLinks = DEFAULT_DROPDOWN_LINKS
29
+ }) {
30
+ const pathname = usePathname() ?? "";
31
+ const active = workspaces.find((w) => w.id === activeWorkspaceId) ?? null;
32
+ const others = workspaces.filter((w) => w.id !== activeWorkspaceId);
33
+ const themeVars = {
34
+ ["--brand-color"]: brandColor,
35
+ ["--brand-soft"]: `${brandColor}26`
36
+ // 15% alpha
37
+ };
38
+ async function switchWorkspace(id) {
39
+ await writeActiveWorkspace(workspacePersist, brandSlug, id, apiSwitchPath);
40
+ if (onWorkspaceSwitch) {
41
+ await onWorkspaceSwitch(id);
42
+ } else if (typeof window !== "undefined") {
43
+ window.location.reload();
44
+ }
45
+ }
46
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ open && /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ onClick: onClose,
51
+ "aria-hidden": "true",
52
+ style: {
53
+ position: "fixed",
54
+ inset: 0,
55
+ background: "rgba(0,0,0,0.5)",
56
+ zIndex: 40
57
+ },
58
+ className: "lg:hidden"
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsxs(
62
+ "aside",
63
+ {
64
+ style: {
65
+ ...themeVars,
66
+ borderRight: "1px solid hsl(var(--border, 220 14% 90%))",
67
+ background: "hsl(var(--card, 0 0% 100%))",
68
+ color: "hsl(var(--foreground, 222 47% 11%))",
69
+ width: 248,
70
+ display: "flex",
71
+ flexDirection: "column"
72
+ },
73
+ className: `fixed inset-y-0 left-0 z-50 h-screen transition-transform lg:sticky lg:top-0 lg:translate-x-0 ${open ? "translate-x-0" : "-translate-x-full"}`,
74
+ children: [
75
+ /* @__PURE__ */ jsxs(
76
+ "div",
77
+ {
78
+ style: {
79
+ padding: "20px 20px 18px",
80
+ borderBottom: "1px solid hsl(var(--border, 220 14% 90%))",
81
+ display: "flex",
82
+ alignItems: "center",
83
+ justifyContent: "space-between"
84
+ },
85
+ children: [
86
+ /* @__PURE__ */ jsxs(
87
+ Link,
88
+ {
89
+ href: "/dashboard",
90
+ onClick: onClose,
91
+ "aria-label": `${brandName} dashboard`,
92
+ style: {
93
+ display: "flex",
94
+ alignItems: "center",
95
+ gap: 8,
96
+ fontSize: 18,
97
+ fontWeight: 700,
98
+ letterSpacing: "-0.02em",
99
+ textDecoration: "none",
100
+ color: "inherit"
101
+ },
102
+ children: [
103
+ brandIcon,
104
+ brandName
105
+ ]
106
+ }
107
+ ),
108
+ /* @__PURE__ */ jsx(
109
+ "button",
110
+ {
111
+ onClick: onClose,
112
+ className: "lg:hidden",
113
+ style: {
114
+ border: "none",
115
+ background: "transparent",
116
+ cursor: "pointer",
117
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
118
+ padding: 4
119
+ },
120
+ "aria-label": "Close navigation",
121
+ children: /* @__PURE__ */ jsx(X, { size: 18 })
122
+ }
123
+ )
124
+ ]
125
+ }
126
+ ),
127
+ /* @__PURE__ */ jsx(
128
+ WorkspaceSwitcher,
129
+ {
130
+ active,
131
+ others,
132
+ hasAny: workspaces.length > 0,
133
+ onSwitch: switchWorkspace,
134
+ onNavigate: onClose
135
+ }
136
+ ),
137
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, padding: "16px 10px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(NavList, { pathname, sections, onNavigate: onClose }) }),
138
+ /* @__PURE__ */ jsx(ProfileDropdown, { user, onLogout, onNavigate: onClose, dropdownLinks })
139
+ ]
140
+ }
141
+ )
142
+ ] });
143
+ }
144
+ function NavList({
145
+ pathname,
146
+ sections,
147
+ onNavigate
148
+ }) {
149
+ const activeHref = activeHrefFor(pathname, sections);
150
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Dashboard", style: { display: "grid", gap: 16 }, children: sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
151
+ /* @__PURE__ */ jsx(
152
+ "div",
153
+ {
154
+ style: {
155
+ fontSize: 10.5,
156
+ letterSpacing: "0.12em",
157
+ textTransform: "uppercase",
158
+ color: "hsl(var(--muted-foreground, 220 9% 46%) / 0.6)",
159
+ padding: "0 10px 6px",
160
+ fontWeight: 600
161
+ },
162
+ children: section.label
163
+ }
164
+ ),
165
+ /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 1 }, children: section.items.map((item) => {
166
+ const isActive = item.href === activeHref;
167
+ const Icon = item.icon;
168
+ const linkStyle = {
169
+ display: "flex",
170
+ alignItems: "center",
171
+ gap: 10,
172
+ fontSize: 13.5,
173
+ fontWeight: isActive ? 600 : 500,
174
+ color: isActive ? "hsl(var(--foreground, 222 47% 11%))" : "hsl(var(--muted-foreground, 220 9% 46%))",
175
+ padding: "7px 10px",
176
+ borderRadius: 8,
177
+ background: isActive ? "var(--brand-soft)" : "transparent",
178
+ cursor: "pointer",
179
+ textDecoration: "none"
180
+ };
181
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, { href: item.href, onClick: onNavigate, style: linkStyle, children: [
182
+ /* @__PURE__ */ jsx(Icon, { size: 15, strokeWidth: 2 }),
183
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: item.label })
184
+ ] }) }, item.href);
185
+ }) })
186
+ ] }, section.label)) });
187
+ }
188
+ function WorkspaceChiclet({ name }) {
189
+ return /* @__PURE__ */ jsx(
190
+ "span",
191
+ {
192
+ "aria-hidden": true,
193
+ style: {
194
+ width: 28,
195
+ height: 28,
196
+ flex: "0 0 28px",
197
+ borderRadius: 8,
198
+ background: "var(--brand-soft)",
199
+ color: "var(--brand-color)",
200
+ display: "inline-flex",
201
+ alignItems: "center",
202
+ justifyContent: "center",
203
+ fontSize: 13,
204
+ fontWeight: 700,
205
+ textTransform: "uppercase",
206
+ border: "1px solid var(--brand-soft)"
207
+ },
208
+ children: name.slice(0, 1)
209
+ }
210
+ );
211
+ }
212
+ function ForjioBadge() {
213
+ return /* @__PURE__ */ jsx(
214
+ "span",
215
+ {
216
+ title: "Forjio-operated workspace",
217
+ style: {
218
+ fontSize: 10,
219
+ textTransform: "uppercase",
220
+ letterSpacing: "0.06em",
221
+ color: "var(--brand-color)",
222
+ background: "var(--brand-soft)",
223
+ border: "1px solid var(--brand-soft)",
224
+ padding: "1px 6px",
225
+ borderRadius: 4,
226
+ flex: "0 0 auto"
227
+ },
228
+ children: "forjio"
229
+ }
230
+ );
231
+ }
232
+ function WorkspaceSwitcher({
233
+ active,
234
+ others,
235
+ hasAny,
236
+ onSwitch,
237
+ onNavigate
238
+ }) {
239
+ const [open, setOpen] = useState(false);
240
+ const ref = useRef(null);
241
+ useEffect(() => {
242
+ function onClick(e) {
243
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
244
+ }
245
+ document.addEventListener("mousedown", onClick);
246
+ return () => document.removeEventListener("mousedown", onClick);
247
+ }, []);
248
+ if (!hasAny) return null;
249
+ return /* @__PURE__ */ jsxs(
250
+ "div",
251
+ {
252
+ ref,
253
+ style: {
254
+ position: "relative",
255
+ padding: "12px 10px",
256
+ borderBottom: "1px solid hsl(var(--border, 220 14% 90%))"
257
+ },
258
+ children: [
259
+ open && others.length > 0 && /* @__PURE__ */ jsxs(
260
+ "div",
261
+ {
262
+ style: {
263
+ position: "absolute",
264
+ top: "100%",
265
+ left: 10,
266
+ right: 10,
267
+ marginTop: 6,
268
+ borderRadius: 10,
269
+ border: "1px solid hsl(var(--border, 220 14% 90%))",
270
+ background: "hsl(var(--card, 0 0% 100%))",
271
+ boxShadow: "0 10px 30px -12px rgba(0, 0, 0, 0.5)",
272
+ padding: 4,
273
+ zIndex: 20
274
+ },
275
+ children: [
276
+ others.map((w) => /* @__PURE__ */ jsxs(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ onClick: () => {
281
+ setOpen(false);
282
+ onSwitch(w.id);
283
+ },
284
+ style: {
285
+ display: "flex",
286
+ alignItems: "center",
287
+ gap: 10,
288
+ width: "100%",
289
+ padding: "8px 10px",
290
+ border: "none",
291
+ background: "transparent",
292
+ textAlign: "left",
293
+ cursor: "pointer",
294
+ borderRadius: 6,
295
+ color: "inherit"
296
+ },
297
+ children: [
298
+ /* @__PURE__ */ jsx(WorkspaceChiclet, { name: w.name }),
299
+ /* @__PURE__ */ jsxs("span", { style: { flex: 1, minWidth: 0 }, children: [
300
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
301
+ /* @__PURE__ */ jsx(
302
+ "span",
303
+ {
304
+ style: {
305
+ fontSize: 13,
306
+ fontWeight: 600,
307
+ whiteSpace: "nowrap",
308
+ overflow: "hidden",
309
+ textOverflow: "ellipsis"
310
+ },
311
+ children: w.name
312
+ }
313
+ ),
314
+ w.isForjioInternal && /* @__PURE__ */ jsx(ForjioBadge, {})
315
+ ] }),
316
+ /* @__PURE__ */ jsx(
317
+ "span",
318
+ {
319
+ style: {
320
+ display: "block",
321
+ fontSize: 11.5,
322
+ color: "hsl(var(--muted-foreground, 220 9% 46%))"
323
+ },
324
+ children: titleCase(w.role)
325
+ }
326
+ )
327
+ ] })
328
+ ]
329
+ },
330
+ w.id
331
+ )),
332
+ /* @__PURE__ */ jsx("div", { style: { borderTop: "1px solid hsl(var(--border, 220 14% 90%))", margin: "4px 0" } }),
333
+ /* @__PURE__ */ jsx(
334
+ Link,
335
+ {
336
+ href: "/dashboard/workspaces",
337
+ onClick: () => {
338
+ setOpen(false);
339
+ onNavigate?.();
340
+ },
341
+ style: {
342
+ display: "flex",
343
+ alignItems: "center",
344
+ gap: 10,
345
+ padding: "8px 10px",
346
+ fontSize: 13,
347
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
348
+ textDecoration: "none",
349
+ borderRadius: 6
350
+ },
351
+ children: "+ Manage workspaces"
352
+ }
353
+ )
354
+ ]
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsxs(
358
+ "button",
359
+ {
360
+ type: "button",
361
+ onClick: () => setOpen((v) => !v),
362
+ disabled: !active,
363
+ style: {
364
+ display: "flex",
365
+ alignItems: "center",
366
+ gap: 10,
367
+ width: "100%",
368
+ padding: "6px 6px",
369
+ border: "none",
370
+ borderRadius: 8,
371
+ background: "transparent",
372
+ cursor: active ? "pointer" : "default",
373
+ textAlign: "left",
374
+ color: "inherit"
375
+ },
376
+ "aria-haspopup": "menu",
377
+ "aria-expanded": open,
378
+ children: [
379
+ /* @__PURE__ */ jsx(WorkspaceChiclet, { name: active?.name ?? "?" }),
380
+ /* @__PURE__ */ jsxs("span", { style: { minWidth: 0, flex: 1 }, children: [
381
+ /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
382
+ /* @__PURE__ */ jsx(
383
+ "span",
384
+ {
385
+ style: {
386
+ fontSize: 13,
387
+ fontWeight: 600,
388
+ whiteSpace: "nowrap",
389
+ overflow: "hidden",
390
+ textOverflow: "ellipsis"
391
+ },
392
+ children: active?.name ?? "Loading\u2026"
393
+ }
394
+ ),
395
+ active?.isForjioInternal && /* @__PURE__ */ jsx(ForjioBadge, {})
396
+ ] }),
397
+ /* @__PURE__ */ jsx(
398
+ "span",
399
+ {
400
+ style: {
401
+ display: "block",
402
+ fontSize: 11.5,
403
+ color: "hsl(var(--muted-foreground, 220 9% 46%))"
404
+ },
405
+ children: active ? titleCase(active.role) : ""
406
+ }
407
+ )
408
+ ] }),
409
+ /* @__PURE__ */ jsx(
410
+ ChevronUp,
411
+ {
412
+ size: 14,
413
+ strokeWidth: 2,
414
+ style: {
415
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
416
+ transform: open ? "rotate(180deg)" : "",
417
+ transition: "transform 120ms ease"
418
+ }
419
+ }
420
+ )
421
+ ]
422
+ }
423
+ )
424
+ ]
425
+ }
426
+ );
427
+ }
428
+ function ProfileDropdown({
429
+ user,
430
+ onLogout,
431
+ onNavigate,
432
+ dropdownLinks
433
+ }) {
434
+ const [open, setOpen] = useState(false);
435
+ const ref = useRef(null);
436
+ useEffect(() => {
437
+ function onClick(e) {
438
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
439
+ }
440
+ document.addEventListener("mousedown", onClick);
441
+ return () => document.removeEventListener("mousedown", onClick);
442
+ }, []);
443
+ const name = user?.name || "You";
444
+ const email = user?.email || "";
445
+ const initial = (user?.name || user?.email || "?").slice(0, 1).toUpperCase();
446
+ const itemStyle = {
447
+ display: "flex",
448
+ alignItems: "center",
449
+ gap: 10,
450
+ padding: "8px 12px",
451
+ fontSize: 13,
452
+ color: "inherit",
453
+ borderRadius: 6,
454
+ textDecoration: "none"
455
+ };
456
+ return /* @__PURE__ */ jsxs(
457
+ "div",
458
+ {
459
+ ref,
460
+ style: {
461
+ position: "relative",
462
+ borderTop: "1px solid hsl(var(--border, 220 14% 90%))",
463
+ padding: "12px 10px"
464
+ },
465
+ children: [
466
+ open && /* @__PURE__ */ jsxs(
467
+ "div",
468
+ {
469
+ style: {
470
+ position: "absolute",
471
+ bottom: "100%",
472
+ left: 10,
473
+ right: 10,
474
+ marginBottom: 6,
475
+ borderRadius: 10,
476
+ border: "1px solid hsl(var(--border, 220 14% 90%))",
477
+ background: "hsl(var(--card, 0 0% 100%))",
478
+ boxShadow: "0 10px 30px -12px rgba(0, 0, 0, 0.5)",
479
+ padding: 4,
480
+ zIndex: 20
481
+ },
482
+ children: [
483
+ /* @__PURE__ */ jsxs(
484
+ "div",
485
+ {
486
+ style: {
487
+ padding: "10px 12px",
488
+ borderBottom: "1px solid hsl(var(--border, 220 14% 90%))"
489
+ },
490
+ children: [
491
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600 }, children: name }),
492
+ /* @__PURE__ */ jsx(
493
+ "div",
494
+ {
495
+ style: {
496
+ fontSize: 12,
497
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
498
+ wordBreak: "break-all"
499
+ },
500
+ children: email
501
+ }
502
+ )
503
+ ]
504
+ }
505
+ ),
506
+ dropdownLinks.map((link) => {
507
+ const Icon = link.icon;
508
+ return /* @__PURE__ */ jsxs(
509
+ Link,
510
+ {
511
+ href: link.href,
512
+ onClick: () => {
513
+ setOpen(false);
514
+ onNavigate?.();
515
+ },
516
+ style: itemStyle,
517
+ children: [
518
+ /* @__PURE__ */ jsx(Icon, { size: 14 }),
519
+ " ",
520
+ link.label
521
+ ]
522
+ },
523
+ link.href
524
+ );
525
+ }),
526
+ /* @__PURE__ */ jsx("div", { style: { borderTop: "1px solid hsl(var(--border, 220 14% 90%))", margin: "4px 0" } }),
527
+ /* @__PURE__ */ jsxs(
528
+ "button",
529
+ {
530
+ type: "button",
531
+ onClick: () => {
532
+ setOpen(false);
533
+ onLogout();
534
+ },
535
+ style: {
536
+ ...itemStyle,
537
+ color: "hsl(var(--destructive, 0 84% 60%))",
538
+ width: "100%",
539
+ border: "none",
540
+ background: "transparent",
541
+ cursor: "pointer",
542
+ textAlign: "left"
543
+ },
544
+ children: [
545
+ /* @__PURE__ */ jsx(LogOut, { size: 14 }),
546
+ " Sign out"
547
+ ]
548
+ }
549
+ )
550
+ ]
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsxs(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ onClick: () => setOpen((v) => !v),
558
+ style: {
559
+ display: "flex",
560
+ alignItems: "center",
561
+ gap: 10,
562
+ width: "100%",
563
+ padding: "8px 8px",
564
+ border: "none",
565
+ borderRadius: 8,
566
+ background: "transparent",
567
+ cursor: "pointer",
568
+ textAlign: "left",
569
+ color: "inherit"
570
+ },
571
+ "aria-haspopup": "menu",
572
+ "aria-expanded": open,
573
+ children: [
574
+ /* @__PURE__ */ jsx(
575
+ "span",
576
+ {
577
+ style: {
578
+ width: 32,
579
+ height: 32,
580
+ flex: "0 0 32px",
581
+ borderRadius: "50%",
582
+ background: "var(--brand-color)",
583
+ color: "#0b0b10",
584
+ display: "inline-flex",
585
+ alignItems: "center",
586
+ justifyContent: "center",
587
+ fontSize: 13,
588
+ fontWeight: 700
589
+ },
590
+ children: initial
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsxs("span", { style: { minWidth: 0, flex: 1 }, children: [
594
+ /* @__PURE__ */ jsx(
595
+ "span",
596
+ {
597
+ style: {
598
+ display: "block",
599
+ fontSize: 13,
600
+ fontWeight: 600,
601
+ whiteSpace: "nowrap",
602
+ overflow: "hidden",
603
+ textOverflow: "ellipsis"
604
+ },
605
+ children: name
606
+ }
607
+ ),
608
+ /* @__PURE__ */ jsx(
609
+ "span",
610
+ {
611
+ style: {
612
+ display: "block",
613
+ fontSize: 11.5,
614
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
615
+ whiteSpace: "nowrap",
616
+ overflow: "hidden",
617
+ textOverflow: "ellipsis"
618
+ },
619
+ children: email
620
+ }
621
+ )
622
+ ] }),
623
+ /* @__PURE__ */ jsx(
624
+ ChevronUp,
625
+ {
626
+ size: 14,
627
+ strokeWidth: 2,
628
+ style: {
629
+ color: "hsl(var(--muted-foreground, 220 9% 46%))",
630
+ transform: open ? "" : "rotate(180deg)",
631
+ transition: "transform 120ms ease"
632
+ }
633
+ }
634
+ )
635
+ ]
636
+ }
637
+ )
638
+ ]
639
+ }
640
+ );
641
+ }
642
+ export {
643
+ Sidebar
644
+ };
645
+ //# sourceMappingURL=Sidebar.js.map