@the-portland-company/shell 0.4.0 → 0.6.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/CHANGELOG.md +18 -0
- package/dist/native.cjs +243 -92
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +50 -5
- package/dist/native.d.ts +50 -5
- package/dist/native.js +244 -93
- package/dist/native.js.map +1 -1
- package/package.json +1 -1
package/dist/native.d.cts
CHANGED
|
@@ -6,9 +6,15 @@ type NavIcon = ComponentType<{
|
|
|
6
6
|
className?: string;
|
|
7
7
|
}>;
|
|
8
8
|
type NavItem = {
|
|
9
|
-
|
|
9
|
+
/** Route to link to. Optional when `onClick` is supplied (renders a button). */
|
|
10
|
+
href?: string;
|
|
10
11
|
label: string;
|
|
11
12
|
icon?: NavIcon;
|
|
13
|
+
/**
|
|
14
|
+
* When supplied the row renders as a `<button>` (styled identically to the
|
|
15
|
+
* link rows) instead of a routed link — used for actions like "Support".
|
|
16
|
+
*/
|
|
17
|
+
onClick?: () => void;
|
|
12
18
|
};
|
|
13
19
|
type NavLinkProps = {
|
|
14
20
|
href: string;
|
|
@@ -19,18 +25,34 @@ type NavLinkProps = {
|
|
|
19
25
|
type NavLinkComponent = ComponentType<NavLinkProps>;
|
|
20
26
|
/** Default active-route test: exact match for "/", prefix match otherwise. */
|
|
21
27
|
declare function isNavItemActive(pathname: string, href: string): boolean;
|
|
22
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Color-coded tone classes for the sidebar environment chip, matching the main
|
|
30
|
+
* VRM app: prod red, staging orange, local/dev green, unknown gray.
|
|
31
|
+
*/
|
|
23
32
|
declare function envChipClasses(label: string): string;
|
|
24
33
|
type NavBrand = {
|
|
25
34
|
/** Where the logo links to. Defaults to "/". */
|
|
26
35
|
homeHref?: string;
|
|
27
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Brand logo image src. When supplied this renders as the wordmark logo
|
|
38
|
+
* (matching the main app's BrandLogo), and `badgeLabel`/`title`/`subtitle`
|
|
39
|
+
* are used only as the no-image fallback.
|
|
40
|
+
*/
|
|
41
|
+
logoSrc?: string;
|
|
42
|
+
logoAlt?: string;
|
|
43
|
+
/** Short label shown inside the brand badge fallback (e.g. "P"). */
|
|
28
44
|
badgeLabel: string;
|
|
29
45
|
title: string;
|
|
30
46
|
subtitle?: string;
|
|
31
47
|
};
|
|
32
48
|
type NavOrg = {
|
|
33
49
|
name: string | null;
|
|
50
|
+
/** Full org id. When present, a copy affordance is shown by the label. */
|
|
51
|
+
id?: string | null;
|
|
52
|
+
/** Optional org logo for the pill badge; falls back to initials. */
|
|
53
|
+
logoUrl?: string | null;
|
|
54
|
+
/** Solid pill background (the org's brand color). Defaults to race red. */
|
|
55
|
+
brandColor?: string | null;
|
|
34
56
|
onClick?: () => void;
|
|
35
57
|
};
|
|
36
58
|
type NavMode = {
|
|
@@ -40,12 +62,17 @@ type NavMode = {
|
|
|
40
62
|
type NavFooterLink = {
|
|
41
63
|
label: string;
|
|
42
64
|
href: string;
|
|
65
|
+
external?: boolean;
|
|
43
66
|
};
|
|
44
67
|
type NavFooter = {
|
|
45
68
|
envLabel: string;
|
|
46
69
|
version: string;
|
|
47
70
|
commit: string;
|
|
48
71
|
links?: NavFooterLink[];
|
|
72
|
+
/** ISO date (or any Date-parseable string) of the deployed commit. */
|
|
73
|
+
commitDate?: string;
|
|
74
|
+
/** Full name of the commit author (only the first name is shown). */
|
|
75
|
+
commitAuthor?: string;
|
|
49
76
|
};
|
|
50
77
|
type NavSidebarProps = {
|
|
51
78
|
pathname: string;
|
|
@@ -63,20 +90,38 @@ type NavSidebarProps = {
|
|
|
63
90
|
declare function NavSidebar({ pathname, brand, nav, org, mode, footer, linkComponent, }: NavSidebarProps): react_jsx_runtime.JSX.Element;
|
|
64
91
|
type NavUser = {
|
|
65
92
|
name: string | null;
|
|
93
|
+
email?: string | null;
|
|
94
|
+
/** Avatar image URL; falls back to initials on the brand color. */
|
|
95
|
+
avatarUrl?: string | null;
|
|
66
96
|
tier?: string | null;
|
|
67
97
|
role?: string | null;
|
|
68
98
|
};
|
|
99
|
+
type NavTopBarAction = {
|
|
100
|
+
key: string;
|
|
101
|
+
icon: NavIcon;
|
|
102
|
+
label: string;
|
|
103
|
+
onClick?: () => void;
|
|
104
|
+
href?: string;
|
|
105
|
+
badge?: number | null;
|
|
106
|
+
tone?: 'default' | 'warning' | 'danger';
|
|
107
|
+
};
|
|
69
108
|
type NavTopBarProps = {
|
|
70
109
|
title: string;
|
|
71
110
|
subtitle?: string;
|
|
72
111
|
user?: NavUser | null;
|
|
73
112
|
onSignOut?: () => void;
|
|
113
|
+
/**
|
|
114
|
+
* Structured icon-button action cluster (32×32 circular buttons with optional
|
|
115
|
+
* count badges), rendered before the `actions` slot. Matches the main app's
|
|
116
|
+
* topbar action icons.
|
|
117
|
+
*/
|
|
118
|
+
actionsList?: NavTopBarAction[];
|
|
74
119
|
/** App-specific action cluster rendered left of the theme toggle. */
|
|
75
120
|
actions?: ReactNode;
|
|
76
121
|
/** Theme toggle slot (injected so this package needs no theme dependency). */
|
|
77
122
|
themeToggle?: ReactNode;
|
|
78
123
|
};
|
|
79
|
-
declare function NavTopBar({ title, subtitle, user, onSignOut, actions, themeToggle, }: NavTopBarProps): react_jsx_runtime.JSX.Element;
|
|
124
|
+
declare function NavTopBar({ title, subtitle, user, onSignOut, actionsList, actions, themeToggle, }: NavTopBarProps): react_jsx_runtime.JSX.Element;
|
|
80
125
|
type NavAppShellProps = {
|
|
81
126
|
sidebar: NavSidebarProps;
|
|
82
127
|
topBar: NavTopBarProps;
|
|
@@ -88,4 +133,4 @@ type NavAppShellProps = {
|
|
|
88
133
|
*/
|
|
89
134
|
declare function NavAppShell({ sidebar, topBar, children }: NavAppShellProps): react_jsx_runtime.JSX.Element;
|
|
90
135
|
|
|
91
|
-
export { NavAppShell, type NavAppShellProps, type NavBrand, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavMode, type NavOrg, NavSidebar, type NavSidebarProps, NavTopBar, type NavTopBarProps, type NavUser, envChipClasses, isNavItemActive };
|
|
136
|
+
export { NavAppShell, type NavAppShellProps, type NavBrand, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavMode, type NavOrg, NavSidebar, type NavSidebarProps, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, envChipClasses, isNavItemActive };
|
package/dist/native.d.ts
CHANGED
|
@@ -6,9 +6,15 @@ type NavIcon = ComponentType<{
|
|
|
6
6
|
className?: string;
|
|
7
7
|
}>;
|
|
8
8
|
type NavItem = {
|
|
9
|
-
|
|
9
|
+
/** Route to link to. Optional when `onClick` is supplied (renders a button). */
|
|
10
|
+
href?: string;
|
|
10
11
|
label: string;
|
|
11
12
|
icon?: NavIcon;
|
|
13
|
+
/**
|
|
14
|
+
* When supplied the row renders as a `<button>` (styled identically to the
|
|
15
|
+
* link rows) instead of a routed link — used for actions like "Support".
|
|
16
|
+
*/
|
|
17
|
+
onClick?: () => void;
|
|
12
18
|
};
|
|
13
19
|
type NavLinkProps = {
|
|
14
20
|
href: string;
|
|
@@ -19,18 +25,34 @@ type NavLinkProps = {
|
|
|
19
25
|
type NavLinkComponent = ComponentType<NavLinkProps>;
|
|
20
26
|
/** Default active-route test: exact match for "/", prefix match otherwise. */
|
|
21
27
|
declare function isNavItemActive(pathname: string, href: string): boolean;
|
|
22
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Color-coded tone classes for the sidebar environment chip, matching the main
|
|
30
|
+
* VRM app: prod red, staging orange, local/dev green, unknown gray.
|
|
31
|
+
*/
|
|
23
32
|
declare function envChipClasses(label: string): string;
|
|
24
33
|
type NavBrand = {
|
|
25
34
|
/** Where the logo links to. Defaults to "/". */
|
|
26
35
|
homeHref?: string;
|
|
27
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Brand logo image src. When supplied this renders as the wordmark logo
|
|
38
|
+
* (matching the main app's BrandLogo), and `badgeLabel`/`title`/`subtitle`
|
|
39
|
+
* are used only as the no-image fallback.
|
|
40
|
+
*/
|
|
41
|
+
logoSrc?: string;
|
|
42
|
+
logoAlt?: string;
|
|
43
|
+
/** Short label shown inside the brand badge fallback (e.g. "P"). */
|
|
28
44
|
badgeLabel: string;
|
|
29
45
|
title: string;
|
|
30
46
|
subtitle?: string;
|
|
31
47
|
};
|
|
32
48
|
type NavOrg = {
|
|
33
49
|
name: string | null;
|
|
50
|
+
/** Full org id. When present, a copy affordance is shown by the label. */
|
|
51
|
+
id?: string | null;
|
|
52
|
+
/** Optional org logo for the pill badge; falls back to initials. */
|
|
53
|
+
logoUrl?: string | null;
|
|
54
|
+
/** Solid pill background (the org's brand color). Defaults to race red. */
|
|
55
|
+
brandColor?: string | null;
|
|
34
56
|
onClick?: () => void;
|
|
35
57
|
};
|
|
36
58
|
type NavMode = {
|
|
@@ -40,12 +62,17 @@ type NavMode = {
|
|
|
40
62
|
type NavFooterLink = {
|
|
41
63
|
label: string;
|
|
42
64
|
href: string;
|
|
65
|
+
external?: boolean;
|
|
43
66
|
};
|
|
44
67
|
type NavFooter = {
|
|
45
68
|
envLabel: string;
|
|
46
69
|
version: string;
|
|
47
70
|
commit: string;
|
|
48
71
|
links?: NavFooterLink[];
|
|
72
|
+
/** ISO date (or any Date-parseable string) of the deployed commit. */
|
|
73
|
+
commitDate?: string;
|
|
74
|
+
/** Full name of the commit author (only the first name is shown). */
|
|
75
|
+
commitAuthor?: string;
|
|
49
76
|
};
|
|
50
77
|
type NavSidebarProps = {
|
|
51
78
|
pathname: string;
|
|
@@ -63,20 +90,38 @@ type NavSidebarProps = {
|
|
|
63
90
|
declare function NavSidebar({ pathname, brand, nav, org, mode, footer, linkComponent, }: NavSidebarProps): react_jsx_runtime.JSX.Element;
|
|
64
91
|
type NavUser = {
|
|
65
92
|
name: string | null;
|
|
93
|
+
email?: string | null;
|
|
94
|
+
/** Avatar image URL; falls back to initials on the brand color. */
|
|
95
|
+
avatarUrl?: string | null;
|
|
66
96
|
tier?: string | null;
|
|
67
97
|
role?: string | null;
|
|
68
98
|
};
|
|
99
|
+
type NavTopBarAction = {
|
|
100
|
+
key: string;
|
|
101
|
+
icon: NavIcon;
|
|
102
|
+
label: string;
|
|
103
|
+
onClick?: () => void;
|
|
104
|
+
href?: string;
|
|
105
|
+
badge?: number | null;
|
|
106
|
+
tone?: 'default' | 'warning' | 'danger';
|
|
107
|
+
};
|
|
69
108
|
type NavTopBarProps = {
|
|
70
109
|
title: string;
|
|
71
110
|
subtitle?: string;
|
|
72
111
|
user?: NavUser | null;
|
|
73
112
|
onSignOut?: () => void;
|
|
113
|
+
/**
|
|
114
|
+
* Structured icon-button action cluster (32×32 circular buttons with optional
|
|
115
|
+
* count badges), rendered before the `actions` slot. Matches the main app's
|
|
116
|
+
* topbar action icons.
|
|
117
|
+
*/
|
|
118
|
+
actionsList?: NavTopBarAction[];
|
|
74
119
|
/** App-specific action cluster rendered left of the theme toggle. */
|
|
75
120
|
actions?: ReactNode;
|
|
76
121
|
/** Theme toggle slot (injected so this package needs no theme dependency). */
|
|
77
122
|
themeToggle?: ReactNode;
|
|
78
123
|
};
|
|
79
|
-
declare function NavTopBar({ title, subtitle, user, onSignOut, actions, themeToggle, }: NavTopBarProps): react_jsx_runtime.JSX.Element;
|
|
124
|
+
declare function NavTopBar({ title, subtitle, user, onSignOut, actionsList, actions, themeToggle, }: NavTopBarProps): react_jsx_runtime.JSX.Element;
|
|
80
125
|
type NavAppShellProps = {
|
|
81
126
|
sidebar: NavSidebarProps;
|
|
82
127
|
topBar: NavTopBarProps;
|
|
@@ -88,4 +133,4 @@ type NavAppShellProps = {
|
|
|
88
133
|
*/
|
|
89
134
|
declare function NavAppShell({ sidebar, topBar, children }: NavAppShellProps): react_jsx_runtime.JSX.Element;
|
|
90
135
|
|
|
91
|
-
export { NavAppShell, type NavAppShellProps, type NavBrand, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavMode, type NavOrg, NavSidebar, type NavSidebarProps, NavTopBar, type NavTopBarProps, type NavUser, envChipClasses, isNavItemActive };
|
|
136
|
+
export { NavAppShell, type NavAppShellProps, type NavBrand, type NavFooter, type NavFooterLink, type NavIcon, type NavItem, type NavLinkComponent, type NavLinkProps, type NavMode, type NavOrg, NavSidebar, type NavSidebarProps, NavTopBar, type NavTopBarAction, type NavTopBarProps, type NavUser, envChipClasses, isNavItemActive };
|
package/dist/native.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
// src/native/index.tsx
|
|
4
5
|
var DefaultLink = ({ href, children, ...rest }) => /* @__PURE__ */ jsx("a", { href, ...rest, children });
|
|
@@ -8,11 +9,15 @@ function isNavItemActive(pathname, href) {
|
|
|
8
9
|
}
|
|
9
10
|
function envChipClasses(label) {
|
|
10
11
|
const l = label.toLowerCase();
|
|
11
|
-
if (l === "production")
|
|
12
|
-
return "bg-[
|
|
13
|
-
if (l === "staging"
|
|
14
|
-
|
|
12
|
+
if (l === "production" || l === "prod")
|
|
13
|
+
return "bg-[#fee2e2] text-[#ef4444]";
|
|
14
|
+
if (l === "staging" || l === "stage")
|
|
15
|
+
return "bg-[#ffedd5] text-[#f97316]";
|
|
16
|
+
if (l === "local" || l === "development" || l === "dev")
|
|
17
|
+
return "bg-[#dcfce7] text-[#22c55e]";
|
|
18
|
+
return "bg-[rgba(107,114,128,0.16)] text-[#6b7280]";
|
|
15
19
|
}
|
|
20
|
+
var ORG_PILL_DEFAULT = "var(--color-race-red)";
|
|
16
21
|
function NavSidebar({
|
|
17
22
|
pathname,
|
|
18
23
|
brand,
|
|
@@ -25,102 +30,202 @@ function NavSidebar({
|
|
|
25
30
|
const Link = linkComponent ?? DefaultLink;
|
|
26
31
|
const activeFn = nav.isActive ?? isNavItemActive;
|
|
27
32
|
const ModeIcon = mode?.icon;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: brand.title }),
|
|
33
|
-
brand.subtitle ? /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: brand.subtitle }) : null
|
|
34
|
-
] })
|
|
35
|
-
] }),
|
|
36
|
-
org ? /* @__PURE__ */ jsxs(
|
|
37
|
-
"button",
|
|
33
|
+
const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
|
|
34
|
+
return /* @__PURE__ */ jsxs("aside", { className: "fixed inset-y-0 left-0 z-30 hidden w-[240px] flex-col border-r border-[color:var(--line)] bg-[color:var(--panel-elevated)] lg:flex", children: [
|
|
35
|
+
/* @__PURE__ */ jsx(
|
|
36
|
+
Link,
|
|
38
37
|
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
href: brand.homeHref ?? "/",
|
|
39
|
+
className: "flex h-[64px] flex-shrink-0 items-center gap-2.5 px-6",
|
|
40
|
+
children: brand.logoSrc ? /* @__PURE__ */ jsx(
|
|
41
|
+
"img",
|
|
42
|
+
{
|
|
43
|
+
src: brand.logoSrc,
|
|
44
|
+
alt: brand.logoAlt ?? brand.title,
|
|
45
|
+
draggable: false,
|
|
46
|
+
className: "h-10 w-auto object-contain"
|
|
47
|
+
}
|
|
48
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 items-center justify-center rounded-xl bg-[color:var(--color-race-red)] text-base font-black text-white shadow-sm", children: brand.badgeLabel }),
|
|
50
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col leading-tight", children: [
|
|
51
|
+
/* @__PURE__ */ jsx("span", { className: "text-base font-extrabold tracking-[-0.01em] text-[color:var(--color-race-red)]", children: brand.title }),
|
|
52
|
+
brand.subtitle ? /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--muted)]", children: brand.subtitle }) : null
|
|
53
|
+
] })
|
|
54
|
+
] })
|
|
49
55
|
}
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const Icon = item.icon;
|
|
60
|
-
return /* @__PURE__ */ jsxs(
|
|
61
|
-
Link,
|
|
56
|
+
),
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-y-auto px-4 pb-2", children: [
|
|
58
|
+
org ? /* @__PURE__ */ jsxs("div", { className: "mb-3", children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between px-1", children: [
|
|
60
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Organization" }),
|
|
61
|
+
org.id ? /* @__PURE__ */ jsx(OrgIdCopyButton, { id: org.id }) : null
|
|
62
|
+
] }),
|
|
63
|
+
/* @__PURE__ */ jsxs(
|
|
64
|
+
"button",
|
|
62
65
|
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
active ? "bg-[color:var(--accent-muted)] text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:bg-[color:var(--panel)] hover:text-[color:var(--foreground)]"
|
|
68
|
-
].join(" "),
|
|
66
|
+
type: "button",
|
|
67
|
+
onClick: org.onClick,
|
|
68
|
+
style: { backgroundColor: orgBg },
|
|
69
|
+
className: "flex w-full items-center justify-between gap-2 rounded-md px-2.5 py-2 text-left text-white shadow-sm transition hover:opacity-95",
|
|
69
70
|
children: [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
) : null,
|
|
77
|
-
item.label
|
|
71
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white/20 text-[11px] font-bold text-white", children: org.logoUrl ? /* @__PURE__ */ jsx("img", { src: org.logoUrl, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase() }),
|
|
73
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: org.name ?? "No organizations" })
|
|
74
|
+
] }),
|
|
75
|
+
/* @__PURE__ */ jsx(ChevronDown, {})
|
|
78
76
|
]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] }) : null,
|
|
80
|
+
mode ? /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxs(
|
|
81
|
+
"button",
|
|
82
|
+
{
|
|
83
|
+
type: "button",
|
|
84
|
+
className: "flex w-full items-center justify-between gap-2 rounded-md bg-[color:var(--color-race-red)] px-2.5 py-2 text-white shadow-sm",
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2.5", children: [
|
|
87
|
+
ModeIcon ? /* @__PURE__ */ jsx(ModeIcon, { size: 16, className: "shrink-0" }) : null,
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tracking-[-0.01em]", children: mode.label })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx(ChevronDown, {})
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
) }) : null,
|
|
94
|
+
/* @__PURE__ */ jsxs("nav", { className: "flex flex-col gap-1", children: [
|
|
95
|
+
nav.groupLabel ? /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: nav.groupLabel }) : null,
|
|
96
|
+
nav.items.map((item, idx) => {
|
|
97
|
+
const active = item.href ? activeFn(pathname, item.href) : false;
|
|
98
|
+
return /* @__PURE__ */ jsx(
|
|
99
|
+
NavRow,
|
|
100
|
+
{
|
|
101
|
+
item,
|
|
102
|
+
active,
|
|
103
|
+
Link
|
|
104
|
+
},
|
|
105
|
+
item.href ?? `nav-${idx}-${item.label}`
|
|
106
|
+
);
|
|
107
|
+
})
|
|
108
|
+
] })
|
|
83
109
|
] }),
|
|
84
|
-
footer ? /* @__PURE__ */ jsxs("div", { className: "
|
|
85
|
-
/* @__PURE__ */
|
|
110
|
+
footer ? /* @__PURE__ */ jsxs("div", { className: "flex-shrink-0 border-t border-[color:var(--line)] px-4 py-3", children: [
|
|
111
|
+
footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-xs", children: footer.links.map((l, idx) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
112
|
+
idx > 0 ? /* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }) : null,
|
|
86
113
|
/* @__PURE__ */ jsx(
|
|
87
|
-
"
|
|
114
|
+
"a",
|
|
88
115
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
children: footer.envLabel
|
|
116
|
+
href: l.href,
|
|
117
|
+
className: "font-medium text-[color:var(--color-race-red)] hover:underline",
|
|
118
|
+
children: l.label
|
|
93
119
|
}
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
)
|
|
121
|
+
] }, l.label)) }) : null,
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-plex-mono)] text-[10px] text-[color:var(--muted)]", children: [
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"span",
|
|
126
|
+
{
|
|
127
|
+
className: [
|
|
128
|
+
"rounded-md px-1.5 py-0.5 text-[10px] font-semibold",
|
|
129
|
+
envChipClasses(footer.envLabel)
|
|
130
|
+
].join(" "),
|
|
131
|
+
children: footer.envLabel
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
135
|
+
/* @__PURE__ */ jsx("span", { children: footer.commit.slice(-4) }),
|
|
136
|
+
/* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
137
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
138
|
+
"v",
|
|
139
|
+
footer.version
|
|
140
|
+
] })
|
|
141
|
+
] }),
|
|
142
|
+
footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
143
|
+
/* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
144
|
+
footer.commitDate ? /* @__PURE__ */ jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
|
|
145
|
+
month: "short",
|
|
146
|
+
day: "numeric"
|
|
147
|
+
}) }) : null,
|
|
148
|
+
/* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" }),
|
|
149
|
+
footer.commitAuthor ? /* @__PURE__ */ jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null,
|
|
150
|
+
/* @__PURE__ */ jsx("span", { className: "text-[color:var(--muted)]", children: "|" })
|
|
151
|
+
] }) : null
|
|
152
|
+
] })
|
|
103
153
|
] }) : null
|
|
104
154
|
] });
|
|
105
155
|
}
|
|
106
|
-
function
|
|
107
|
-
return /* @__PURE__ */
|
|
156
|
+
function ChevronDown() {
|
|
157
|
+
return /* @__PURE__ */ jsx(
|
|
108
158
|
"svg",
|
|
109
159
|
{
|
|
110
|
-
width:
|
|
111
|
-
height:
|
|
160
|
+
width: 16,
|
|
161
|
+
height: 16,
|
|
112
162
|
viewBox: "0 0 24 24",
|
|
113
163
|
fill: "none",
|
|
114
164
|
stroke: "currentColor",
|
|
115
165
|
strokeWidth: 2,
|
|
116
166
|
strokeLinecap: "round",
|
|
117
167
|
strokeLinejoin: "round",
|
|
118
|
-
className: "shrink-0
|
|
168
|
+
className: "shrink-0 opacity-80",
|
|
119
169
|
"aria-hidden": "true",
|
|
120
|
-
children:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
170
|
+
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
var NAV_HOVER_GRADIENT = "linear-gradient(90deg,#ea6a3a 0%,#E63946 100%)";
|
|
175
|
+
function NavRow({
|
|
176
|
+
item,
|
|
177
|
+
active,
|
|
178
|
+
Link
|
|
179
|
+
}) {
|
|
180
|
+
const Icon = item.icon;
|
|
181
|
+
const wrapperClasses = [
|
|
182
|
+
"group relative block overflow-hidden rounded-md transition",
|
|
183
|
+
active ? "bg-[color:var(--color-race-red)] text-white shadow-sm" : "text-[color:var(--muted)] hover:text-white"
|
|
184
|
+
].join(" ");
|
|
185
|
+
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
186
|
+
!active ? /* @__PURE__ */ jsx(
|
|
187
|
+
"span",
|
|
188
|
+
{
|
|
189
|
+
"aria-hidden": "true",
|
|
190
|
+
className: "pointer-events-none absolute inset-y-0 left-0 w-0 transition-[width] duration-300 group-hover:w-full",
|
|
191
|
+
style: { background: NAV_HOVER_GRADIENT }
|
|
192
|
+
}
|
|
193
|
+
) : null,
|
|
194
|
+
/* @__PURE__ */ jsxs("span", { className: "relative z-10 flex items-center gap-3 px-3 py-2 text-sm font-semibold", children: [
|
|
195
|
+
Icon ? /* @__PURE__ */ jsx(Icon, { size: 18, className: active ? "text-white" : "text-current" }) : null,
|
|
196
|
+
item.label
|
|
197
|
+
] })
|
|
198
|
+
] });
|
|
199
|
+
if (item.onClick && !item.href) {
|
|
200
|
+
return /* @__PURE__ */ jsx("button", { type: "button", onClick: item.onClick, className: `w-full text-left ${wrapperClasses}`, children: inner });
|
|
201
|
+
}
|
|
202
|
+
return /* @__PURE__ */ jsx(
|
|
203
|
+
Link,
|
|
204
|
+
{
|
|
205
|
+
href: item.href ?? "#",
|
|
206
|
+
"aria-current": active ? "page" : void 0,
|
|
207
|
+
className: wrapperClasses,
|
|
208
|
+
children: inner
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function OrgIdCopyButton({ id }) {
|
|
213
|
+
const [copied, setCopied] = useState(false);
|
|
214
|
+
return /* @__PURE__ */ jsx(
|
|
215
|
+
"button",
|
|
216
|
+
{
|
|
217
|
+
type: "button",
|
|
218
|
+
onClick: () => {
|
|
219
|
+
try {
|
|
220
|
+
void navigator.clipboard?.writeText(id);
|
|
221
|
+
} catch {
|
|
222
|
+
}
|
|
223
|
+
setCopied(true);
|
|
224
|
+
setTimeout(() => setCopied(false), 1500);
|
|
225
|
+
},
|
|
226
|
+
title: "Copy organization id",
|
|
227
|
+
className: "rounded px-1 text-[11px] text-[color:var(--muted)] hover:bg-[color:var(--panel)]",
|
|
228
|
+
children: copied ? "Copied" : `...${id.slice(-8)}`
|
|
124
229
|
}
|
|
125
230
|
);
|
|
126
231
|
}
|
|
@@ -129,16 +234,18 @@ function NavTopBar({
|
|
|
129
234
|
subtitle,
|
|
130
235
|
user,
|
|
131
236
|
onSignOut,
|
|
237
|
+
actionsList,
|
|
132
238
|
actions,
|
|
133
239
|
themeToggle
|
|
134
240
|
}) {
|
|
135
241
|
const initials = (user?.name ?? "Politogy").slice(0, 2).toUpperCase();
|
|
136
|
-
|
|
242
|
+
const secondary = user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
|
|
243
|
+
return /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-20 flex h-20 items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 backdrop-blur-xl", children: [
|
|
137
244
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
138
245
|
/* @__PURE__ */ jsx(
|
|
139
246
|
"h1",
|
|
140
247
|
{
|
|
141
|
-
className: "truncate text-
|
|
248
|
+
className: "truncate text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]",
|
|
142
249
|
"data-display": "true",
|
|
143
250
|
children: title
|
|
144
251
|
}
|
|
@@ -146,24 +253,30 @@ function NavTopBar({
|
|
|
146
253
|
subtitle ? /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-[color:var(--muted)]", children: subtitle }) : null
|
|
147
254
|
] }),
|
|
148
255
|
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
256
|
+
actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsx(NavTopBarActionButton, { action }, action.key)) : null,
|
|
149
257
|
actions,
|
|
150
258
|
themeToggle,
|
|
151
|
-
/* @__PURE__ */ jsxs("
|
|
152
|
-
/* @__PURE__ */
|
|
153
|
-
|
|
154
|
-
/* @__PURE__ */
|
|
155
|
-
|
|
259
|
+
/* @__PURE__ */ jsxs("details", { className: "group relative", children: [
|
|
260
|
+
/* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none items-center gap-2.5 rounded-full py-1 pl-1 pr-2 transition hover:bg-[color:var(--panel-elevated)] [&::-webkit-details-marker]:hidden", children: [
|
|
261
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-9 w-9 items-center justify-center overflow-hidden rounded-full bg-[color:var(--color-race-red)] text-xs font-bold text-white", children: user?.avatarUrl ? /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: "", className: "h-full w-full object-cover" }) : initials }),
|
|
262
|
+
/* @__PURE__ */ jsxs("span", { className: "hidden flex-col leading-tight md:flex", children: [
|
|
263
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: user?.name ?? "Signed in" }),
|
|
264
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-[color:var(--muted)]", children: secondary })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsx(ChevronDown, {})
|
|
156
267
|
] }),
|
|
157
|
-
onSignOut ? /* @__PURE__ */ jsx(
|
|
268
|
+
onSignOut ? /* @__PURE__ */ jsx("div", { className: "absolute right-0 z-30 mt-2 w-44 overflow-hidden rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: /* @__PURE__ */ jsxs(
|
|
158
269
|
"button",
|
|
159
270
|
{
|
|
160
271
|
type: "button",
|
|
161
272
|
onClick: onSignOut,
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
|
|
273
|
+
className: "flex w-full items-center gap-2.5 px-4 py-2 text-left text-sm font-medium text-[color:var(--foreground)] hover:bg-[color:var(--panel)] hover:text-[color:var(--color-race-red)]",
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx(SignOutIcon, {}),
|
|
276
|
+
"Sign out"
|
|
277
|
+
]
|
|
165
278
|
}
|
|
166
|
-
) : null
|
|
279
|
+
) }) : null
|
|
167
280
|
] })
|
|
168
281
|
] })
|
|
169
282
|
] });
|
|
@@ -189,10 +302,48 @@ function SignOutIcon() {
|
|
|
189
302
|
}
|
|
190
303
|
);
|
|
191
304
|
}
|
|
305
|
+
function NavTopBarActionButton({ action }) {
|
|
306
|
+
const Icon = action.icon;
|
|
307
|
+
const tone = action.tone ?? "default";
|
|
308
|
+
const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--color-race-red)] hover:text-[color:var(--color-race-red)]" : "text-[color:var(--muted)] hover:text-[color:var(--foreground)]";
|
|
309
|
+
const className = [
|
|
310
|
+
"relative flex h-8 w-8 items-center justify-center rounded-full border border-[color:var(--line)] transition",
|
|
311
|
+
toneClass
|
|
312
|
+
].join(" ");
|
|
313
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
314
|
+
/* @__PURE__ */ jsx(Icon, { size: 20, className: "text-current" }),
|
|
315
|
+
typeof action.badge === "number" && action.badge > 0 ? /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-[color:var(--color-race-red)] px-1 text-[10px] text-white", children: action.badge }) : null
|
|
316
|
+
] });
|
|
317
|
+
const isExternal = action.href ? /^https?:\/\//.test(action.href) : false;
|
|
318
|
+
if (action.href) {
|
|
319
|
+
return /* @__PURE__ */ jsx(
|
|
320
|
+
"a",
|
|
321
|
+
{
|
|
322
|
+
href: action.href,
|
|
323
|
+
"aria-label": action.label,
|
|
324
|
+
title: action.label,
|
|
325
|
+
className,
|
|
326
|
+
...isExternal ? { target: "_blank", rel: "noreferrer" } : {},
|
|
327
|
+
children: content
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return /* @__PURE__ */ jsx(
|
|
332
|
+
"button",
|
|
333
|
+
{
|
|
334
|
+
type: "button",
|
|
335
|
+
onClick: action.onClick,
|
|
336
|
+
"aria-label": action.label,
|
|
337
|
+
title: action.label,
|
|
338
|
+
className,
|
|
339
|
+
children: content
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
192
343
|
function NavAppShell({ sidebar, topBar, children }) {
|
|
193
344
|
return /* @__PURE__ */ jsxs("div", { className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]", children: [
|
|
194
345
|
/* @__PURE__ */ jsx(NavSidebar, { ...sidebar }),
|
|
195
|
-
/* @__PURE__ */ jsxs("div", { className: "lg:pl-[
|
|
346
|
+
/* @__PURE__ */ jsxs("div", { className: "lg:pl-[240px]", children: [
|
|
196
347
|
/* @__PURE__ */ jsx(NavTopBar, { ...topBar }),
|
|
197
348
|
/* @__PURE__ */ jsx("main", { className: "px-6 py-7", children: /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl", children }) })
|
|
198
349
|
] })
|