@bizbasics/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,347 @@
1
+ "use client";
2
+
3
+ import { useRef, useState } from "react";
4
+ import type { CSSProperties } from "react";
5
+
6
+ export interface PlatformNavApp {
7
+ id: string;
8
+ name: string;
9
+ frontendUrl: string;
10
+ iconUrl?: string | null;
11
+ color?: string;
12
+ }
13
+
14
+ export interface PlatformNavUser {
15
+ name: string;
16
+ email: string;
17
+ avatarUrl?: string | null;
18
+ }
19
+
20
+ export interface PlatformNavProps {
21
+ /** Current app identifier (highlighted in app switcher) */
22
+ currentApp: string;
23
+ /** Org display name */
24
+ orgName: string;
25
+ /** Authenticated user */
26
+ user: PlatformNavUser;
27
+ /** Apps the org is entitled to */
28
+ apps: PlatformNavApp[];
29
+ /**
30
+ * Called when user clicks a different app.
31
+ * Implementation should mint an SSO token and redirect the user.
32
+ * Defaults to: window.open(`${app.frontendUrl}/auth/sso?token=<token>`)
33
+ */
34
+ onSwitchApp?: (app: PlatformNavApp) => void;
35
+ /** Called when user clicks the profile/avatar area */
36
+ onOpenProfile?: () => void;
37
+ /** Portal URL — used for "Back to dashboard" link */
38
+ portalUrl?: string;
39
+ }
40
+
41
+ export function PlatformNav({
42
+ currentApp,
43
+ orgName,
44
+ user,
45
+ apps,
46
+ onSwitchApp,
47
+ onOpenProfile,
48
+ portalUrl = "https://app.bizbasics.ai",
49
+ }: PlatformNavProps) {
50
+ const [switcherOpen, setSwitcherOpen] = useState(false);
51
+ const switcherRef = useRef<HTMLDivElement>(null);
52
+
53
+ function handleSwitchApp(app: PlatformNavApp) {
54
+ setSwitcherOpen(false);
55
+ if (app.id === currentApp) return;
56
+ if (onSwitchApp) {
57
+ onSwitchApp(app);
58
+ } else {
59
+ window.open(app.frontendUrl, "_blank");
60
+ }
61
+ }
62
+
63
+ return (
64
+ <nav style={navStyle}>
65
+ {/* Left: app switcher */}
66
+ <div style={leftStyle}>
67
+ <a href={portalUrl} style={logoLinkStyle} title="Back to dashboard">
68
+ <span style={logoDotStyle} />
69
+ <span style={logoTextStyle}>bizbasics</span>
70
+ </a>
71
+
72
+ <div style={dividerStyle} />
73
+
74
+ <div ref={switcherRef} style={{ position: "relative" }}>
75
+ <button
76
+ style={switcherBtnStyle}
77
+ onClick={() => setSwitcherOpen((v) => !v)}
78
+ aria-haspopup="listbox"
79
+ aria-expanded={switcherOpen}
80
+ >
81
+ <span style={currentAppLabelStyle}>
82
+ {apps.find((a) => a.id === currentApp)?.name ?? currentApp}
83
+ </span>
84
+ <ChevronDown />
85
+ </button>
86
+
87
+ {switcherOpen && (
88
+ <>
89
+ <div style={overlayStyle} onClick={() => setSwitcherOpen(false)} />
90
+ <div style={dropdownStyle} role="listbox">
91
+ {apps.map((app) => (
92
+ <button
93
+ key={app.id}
94
+ role="option"
95
+ aria-selected={app.id === currentApp}
96
+ style={appItemStyle(app.id === currentApp)}
97
+ onClick={() => handleSwitchApp(app)}
98
+ >
99
+ {app.iconUrl ? (
100
+ <img src={app.iconUrl} alt="" style={appIconImgStyle} />
101
+ ) : (
102
+ <span style={appIconFallbackStyle(app.color)}>{app.name[0]}</span>
103
+ )}
104
+ <span style={appNameStyle}>{app.name}</span>
105
+ {app.id === currentApp && <CheckIcon />}
106
+ </button>
107
+ ))}
108
+ <div style={switcherFooterStyle}>
109
+ <a href={portalUrl} style={dashboardLinkStyle}>
110
+ Go to dashboard →
111
+ </a>
112
+ </div>
113
+ </div>
114
+ </>
115
+ )}
116
+ </div>
117
+ </div>
118
+
119
+ {/* Right: org name + avatar */}
120
+ <div style={rightStyle}>
121
+ <span style={orgNameStyle}>{orgName}</span>
122
+ <button
123
+ style={avatarBtnStyle}
124
+ onClick={onOpenProfile}
125
+ title={`${user.name} (${user.email})`}
126
+ aria-label="Open profile"
127
+ >
128
+ {user.avatarUrl ? (
129
+ <img src={user.avatarUrl} alt={user.name} style={avatarImgStyle} />
130
+ ) : (
131
+ <span style={avatarFallbackStyle}>
132
+ {user.name[0]?.toUpperCase() ?? "?"}
133
+ </span>
134
+ )}
135
+ </button>
136
+ </div>
137
+ </nav>
138
+ );
139
+ }
140
+
141
+ // ── Icons ─────────────────────────────────────────────────────────────────────
142
+
143
+ function ChevronDown() {
144
+ return (
145
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ marginLeft: "4px", opacity: 0.6 }}>
146
+ <path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
147
+ </svg>
148
+ );
149
+ }
150
+
151
+ function CheckIcon() {
152
+ return (
153
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" style={{ marginLeft: "auto", color: "var(--bb-brand, #6366f1)" }}>
154
+ <path d="M2 7l3.5 3.5 6.5-7" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
155
+ </svg>
156
+ );
157
+ }
158
+
159
+ // ── Styles ────────────────────────────────────────────────────────────────────
160
+
161
+ const navStyle: CSSProperties = {
162
+ display: "flex",
163
+ alignItems: "center",
164
+ justifyContent: "space-between",
165
+ height: "44px",
166
+ padding: "0 16px",
167
+ background: "#1e1e2e",
168
+ borderBottom: "1px solid rgba(255,255,255,0.08)",
169
+ flexShrink: 0,
170
+ fontFamily: "inherit",
171
+ };
172
+
173
+ const leftStyle: CSSProperties = {
174
+ display: "flex",
175
+ alignItems: "center",
176
+ gap: "0",
177
+ };
178
+
179
+ const rightStyle: CSSProperties = {
180
+ display: "flex",
181
+ alignItems: "center",
182
+ gap: "12px",
183
+ };
184
+
185
+ const logoLinkStyle: CSSProperties = {
186
+ display: "flex",
187
+ alignItems: "center",
188
+ gap: "6px",
189
+ textDecoration: "none",
190
+ marginRight: "12px",
191
+ };
192
+
193
+ const logoDotStyle: CSSProperties = {
194
+ width: "8px",
195
+ height: "8px",
196
+ borderRadius: "50%",
197
+ background: "#6366f1",
198
+ };
199
+
200
+ const logoTextStyle: CSSProperties = {
201
+ fontSize: "13px",
202
+ fontWeight: 600,
203
+ color: "rgba(255,255,255,0.9)",
204
+ letterSpacing: "-0.01em",
205
+ };
206
+
207
+ const dividerStyle: CSSProperties = {
208
+ width: "1px",
209
+ height: "18px",
210
+ background: "rgba(255,255,255,0.12)",
211
+ margin: "0 12px",
212
+ };
213
+
214
+ const switcherBtnStyle: CSSProperties = {
215
+ display: "flex",
216
+ alignItems: "center",
217
+ background: "transparent",
218
+ border: "none",
219
+ cursor: "pointer",
220
+ color: "rgba(255,255,255,0.85)",
221
+ padding: "4px 8px",
222
+ borderRadius: "6px",
223
+ fontSize: "13px",
224
+ fontWeight: 500,
225
+ transition: "background 0.15s",
226
+ };
227
+
228
+ const currentAppLabelStyle: CSSProperties = {
229
+ fontWeight: 600,
230
+ };
231
+
232
+ const overlayStyle: CSSProperties = {
233
+ position: "fixed",
234
+ inset: 0,
235
+ zIndex: 100,
236
+ };
237
+
238
+ const dropdownStyle: CSSProperties = {
239
+ position: "absolute",
240
+ top: "calc(100% + 6px)",
241
+ left: 0,
242
+ background: "#2a2a3e",
243
+ border: "1px solid rgba(255,255,255,0.1)",
244
+ borderRadius: "10px",
245
+ padding: "6px",
246
+ minWidth: "200px",
247
+ boxShadow: "0 8px 24px rgba(0,0,0,0.4)",
248
+ zIndex: 101,
249
+ };
250
+
251
+ function appItemStyle(active: boolean): CSSProperties {
252
+ return {
253
+ display: "flex",
254
+ alignItems: "center",
255
+ gap: "10px",
256
+ width: "100%",
257
+ padding: "8px 10px",
258
+ borderRadius: "6px",
259
+ background: active ? "rgba(99,102,241,0.15)" : "transparent",
260
+ border: "none",
261
+ cursor: "pointer",
262
+ color: "rgba(255,255,255,0.85)",
263
+ fontSize: "13px",
264
+ fontWeight: active ? 600 : 400,
265
+ textAlign: "left",
266
+ transition: "background 0.12s",
267
+ };
268
+ }
269
+
270
+ const appIconImgStyle: CSSProperties = {
271
+ width: "20px",
272
+ height: "20px",
273
+ borderRadius: "4px",
274
+ objectFit: "cover",
275
+ flexShrink: 0,
276
+ };
277
+
278
+ function appIconFallbackStyle(color?: string): CSSProperties {
279
+ return {
280
+ width: "20px",
281
+ height: "20px",
282
+ borderRadius: "4px",
283
+ background: color ?? "#6366f1",
284
+ color: "#fff",
285
+ fontSize: "11px",
286
+ fontWeight: 700,
287
+ display: "flex",
288
+ alignItems: "center",
289
+ justifyContent: "center",
290
+ flexShrink: 0,
291
+ };
292
+ }
293
+
294
+ const appNameStyle: CSSProperties = {
295
+ flex: 1,
296
+ };
297
+
298
+ const switcherFooterStyle: CSSProperties = {
299
+ borderTop: "1px solid rgba(255,255,255,0.08)",
300
+ marginTop: "6px",
301
+ paddingTop: "6px",
302
+ };
303
+
304
+ const dashboardLinkStyle: CSSProperties = {
305
+ display: "block",
306
+ padding: "6px 10px",
307
+ fontSize: "12px",
308
+ color: "rgba(255,255,255,0.45)",
309
+ textDecoration: "none",
310
+ };
311
+
312
+ const orgNameStyle: CSSProperties = {
313
+ fontSize: "12px",
314
+ color: "rgba(255,255,255,0.45)",
315
+ fontWeight: 500,
316
+ };
317
+
318
+ const avatarBtnStyle: CSSProperties = {
319
+ background: "transparent",
320
+ border: "none",
321
+ cursor: "pointer",
322
+ padding: 0,
323
+ borderRadius: "50%",
324
+ display: "flex",
325
+ alignItems: "center",
326
+ justifyContent: "center",
327
+ };
328
+
329
+ const avatarImgStyle: CSSProperties = {
330
+ width: "28px",
331
+ height: "28px",
332
+ borderRadius: "50%",
333
+ objectFit: "cover",
334
+ };
335
+
336
+ const avatarFallbackStyle: CSSProperties = {
337
+ width: "28px",
338
+ height: "28px",
339
+ borderRadius: "50%",
340
+ background: "#6366f1",
341
+ color: "#fff",
342
+ fontSize: "12px",
343
+ fontWeight: 700,
344
+ display: "flex",
345
+ alignItems: "center",
346
+ justifyContent: "center",
347
+ };
@@ -0,0 +1,184 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, type ReactNode } from "react";
4
+ import type { CSSProperties } from "react";
5
+ import { Avatar } from "./Avatar";
6
+
7
+ export interface ProfileCalloutUser {
8
+ id: string;
9
+ name: string;
10
+ email: string;
11
+ role?: string;
12
+ }
13
+
14
+ interface MenuItem {
15
+ label: string;
16
+ icon?: ReactNode;
17
+ href?: string;
18
+ onClick?: () => void;
19
+ danger?: boolean;
20
+ divider?: boolean;
21
+ }
22
+
23
+ interface Props {
24
+ user: ProfileCalloutUser;
25
+ /** Extra menu items before sign out — e.g. status picker, profile settings */
26
+ extraItems?: MenuItem[];
27
+ onSignOut: () => void;
28
+ settingsHref?: string;
29
+ /** Slot below avatar row — e.g. StatusPicker for relay */
30
+ statusSlot?: ReactNode;
31
+ }
32
+
33
+ const containerStyle: CSSProperties = {
34
+ padding: "8px",
35
+ borderTop: "1px solid var(--bb-sidebar-border)",
36
+ };
37
+
38
+ const profileRowStyle: CSSProperties = {
39
+ display: "flex",
40
+ alignItems: "center",
41
+ gap: "10px",
42
+ padding: "6px 8px",
43
+ borderRadius: "var(--bb-radius)",
44
+ cursor: "pointer",
45
+ userSelect: "none",
46
+ transition: "background var(--bb-transition)",
47
+ width: "100%",
48
+ border: "none",
49
+ background: "transparent",
50
+ textAlign: "left",
51
+ };
52
+
53
+ const menuStyle: CSSProperties = {
54
+ position: "absolute",
55
+ bottom: "calc(100% + 6px)",
56
+ left: "8px",
57
+ right: "8px",
58
+ background: "var(--bb-topbar-bg)",
59
+ border: "1px solid rgba(255,255,255,0.10)",
60
+ borderRadius: "var(--bb-radius-md)",
61
+ boxShadow: "var(--bb-shadow-xl)",
62
+ zIndex: "var(--bb-z-dropdown)" as unknown as number,
63
+ overflow: "hidden",
64
+ };
65
+
66
+ export function ProfileCallout({ user, extraItems, onSignOut, settingsHref, statusSlot }: Props) {
67
+ const [open, setOpen] = useState(false);
68
+ const ref = useRef<HTMLDivElement>(null);
69
+
70
+ useEffect(() => {
71
+ if (!open) return;
72
+ const handler = (e: MouseEvent) => {
73
+ if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
74
+ };
75
+ document.addEventListener("mousedown", handler);
76
+ return () => document.removeEventListener("mousedown", handler);
77
+ }, [open]);
78
+
79
+ const allItems: MenuItem[] = [
80
+ ...(settingsHref ? [{ label: "Settings", href: settingsHref }] : []),
81
+ ...(extraItems ?? []),
82
+ { divider: true, label: "" },
83
+ { label: "Sign out", onClick: onSignOut, danger: true },
84
+ ];
85
+
86
+ return (
87
+ <div style={{ ...containerStyle, position: "relative" }} ref={ref}>
88
+ {statusSlot}
89
+
90
+ <button
91
+ style={profileRowStyle}
92
+ onClick={() => setOpen((p) => !p)}
93
+ onMouseEnter={(e) => { (e.currentTarget).style.background = "var(--bb-sidebar-hover-bg)"; }}
94
+ onMouseLeave={(e) => { (e.currentTarget).style.background = "transparent"; }}
95
+ >
96
+ <Avatar name={user.name} size={30} />
97
+ <div style={{ flex: 1, minWidth: 0 }}>
98
+ <div style={{
99
+ fontSize: "var(--bb-text-sm)",
100
+ fontWeight: 600,
101
+ color: "var(--bb-sidebar-text)",
102
+ overflow: "hidden",
103
+ textOverflow: "ellipsis",
104
+ whiteSpace: "nowrap",
105
+ lineHeight: 1.3,
106
+ }}>
107
+ {user.name}
108
+ </div>
109
+ <div style={{
110
+ fontSize: "var(--bb-text-xs)",
111
+ color: "var(--bb-sidebar-text-muted)",
112
+ overflow: "hidden",
113
+ textOverflow: "ellipsis",
114
+ whiteSpace: "nowrap",
115
+ lineHeight: 1.3,
116
+ }}>
117
+ {user.role ? `${user.role} · ` : ""}{user.email}
118
+ </div>
119
+ </div>
120
+ <svg
121
+ width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--bb-sidebar-text-muted)"
122
+ strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
123
+ style={{ flexShrink: 0, transform: open ? "rotate(180deg)" : "none", transition: "transform var(--bb-transition)" }}
124
+ >
125
+ <polyline points="6 9 12 15 18 9" />
126
+ </svg>
127
+ </button>
128
+
129
+ {open && (
130
+ <div style={menuStyle} role="menu">
131
+ {/* User header */}
132
+ <div style={{ padding: "12px 14px", borderBottom: "1px solid rgba(255,255,255,0.07)" }}>
133
+ <div style={{ fontSize: "var(--bb-text-sm)", fontWeight: 600, color: "#e5e7eb" }}>{user.name}</div>
134
+ <div style={{ fontSize: "var(--bb-text-xs)", color: "#6b7280", marginTop: "2px" }}>{user.email}</div>
135
+ </div>
136
+
137
+ {/* Menu items */}
138
+ <div style={{ padding: "4px" }}>
139
+ {allItems.map((item, i) =>
140
+ item.divider ? (
141
+ <div key={i} style={{ height: "1px", background: "rgba(255,255,255,0.07)", margin: "4px 0" }} />
142
+ ) : item.href ? (
143
+ <a
144
+ key={i}
145
+ href={item.href}
146
+ style={menuItemStyle(false, item.danger)}
147
+ onClick={() => setOpen(false)}
148
+ >
149
+ {item.icon && <span style={{ width: 16, display: "flex" }}>{item.icon}</span>}
150
+ {item.label}
151
+ </a>
152
+ ) : (
153
+ <button
154
+ key={i}
155
+ onClick={() => { item.onClick?.(); setOpen(false); }}
156
+ style={{ ...menuItemStyle(false, item.danger), width: "100%", border: "none", textAlign: "left", cursor: "pointer" }}
157
+ >
158
+ {item.icon && <span style={{ width: 16, display: "flex" }}>{item.icon}</span>}
159
+ {item.label}
160
+ </button>
161
+ )
162
+ )}
163
+ </div>
164
+ </div>
165
+ )}
166
+ </div>
167
+ );
168
+ }
169
+
170
+ function menuItemStyle(active: boolean, danger?: boolean): CSSProperties {
171
+ return {
172
+ display: "flex",
173
+ alignItems: "center",
174
+ gap: "8px",
175
+ padding: "7px 10px",
176
+ borderRadius: "var(--bb-radius-sm)",
177
+ fontSize: "var(--bb-text-sm)",
178
+ color: danger ? "#f87171" : "#d1d5db",
179
+ background: active ? "rgba(255,255,255,0.08)" : "transparent",
180
+ textDecoration: "none",
181
+ transition: "background var(--bb-transition), color var(--bb-transition)",
182
+ userSelect: "none",
183
+ };
184
+ }
@@ -0,0 +1,162 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+
3
+ interface Props {
4
+ /** Platform name — defaults to "bizbasics" */
5
+ name?: string;
6
+ /** App/product name shown below, e.g. "Portal", "Relay", "Monk" */
7
+ appName?: string;
8
+ /** Extra content to the right of the name (e.g. org switcher) */
9
+ right?: ReactNode;
10
+ }
11
+
12
+ const BB_LOGO = (
13
+ <svg width="26" height="26" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
14
+ <rect width="40" height="40" rx="9" fill="#0f172a"/>
15
+ <text x="5" y="29" fontFamily="system-ui,sans-serif" fontWeight="900" fontSize="24" fill="url(#bbg1)">B</text>
16
+ <text x="16" y="29" fontFamily="system-ui,sans-serif" fontWeight="900" fontSize="24" fill="url(#bbg2)" opacity="0.65">B</text>
17
+ <circle cx="33" cy="9" r="4" fill="#2563eb"/>
18
+ <defs>
19
+ <linearGradient id="bbg1" x1="5" y1="8" x2="22" y2="30" gradientUnits="userSpaceOnUse">
20
+ <stop stopColor="#2563eb"/><stop offset="1" stopColor="#60a5fa"/>
21
+ </linearGradient>
22
+ <linearGradient id="bbg2" x1="16" y1="8" x2="33" y2="30" gradientUnits="userSpaceOnUse">
23
+ <stop stopColor="#60a5fa"/><stop offset="1" stopColor="#2563eb"/>
24
+ </linearGradient>
25
+ </defs>
26
+ </svg>
27
+ );
28
+
29
+ const wrapStyle: CSSProperties = {
30
+ padding: "14px 14px 12px",
31
+ };
32
+
33
+ const rowStyle: CSSProperties = {
34
+ display: "flex",
35
+ alignItems: "center",
36
+ justifyContent: "space-between",
37
+ };
38
+
39
+ const nameRowStyle: CSSProperties = {
40
+ display: "flex",
41
+ alignItems: "center",
42
+ gap: "9px",
43
+ };
44
+
45
+ export function SidebarBrand({ name = "bizbasics", appName, right }: Props) {
46
+ return (
47
+ <div style={wrapStyle}>
48
+ <div style={rowStyle}>
49
+ <div style={nameRowStyle}>
50
+ {BB_LOGO}
51
+ <div>
52
+ <div style={{
53
+ fontSize: "var(--bb-text-sm)",
54
+ fontWeight: 700,
55
+ color: "#fff",
56
+ lineHeight: 1.2,
57
+ letterSpacing: "-0.01em",
58
+ }}>
59
+ {name}
60
+ </div>
61
+ {appName && (
62
+ <div style={{
63
+ fontSize: "var(--bb-text-xs)",
64
+ color: "var(--bb-sidebar-text-muted)",
65
+ lineHeight: 1.2,
66
+ marginTop: "1px",
67
+ }}>
68
+ {appName}
69
+ </div>
70
+ )}
71
+ </div>
72
+ </div>
73
+ {right}
74
+ </div>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ /** Org/workspace switcher — used by portal */
80
+ export function OrgSwitcher({
81
+ current,
82
+ orgs,
83
+ onChange,
84
+ disabled,
85
+ }: {
86
+ current: string;
87
+ orgs: Array<{ id: string; name: string; is_sandbox?: boolean }>;
88
+ onChange: (id: string) => void;
89
+ disabled?: boolean;
90
+ }) {
91
+ const orgList = orgs ?? [];
92
+
93
+ // Native <option> can't render rich badges, so we suffix the label.
94
+ // The full amber SandboxBanner in the portal layout is the primary signal;
95
+ // this is just so the org switcher itself doesn't lie about which is which.
96
+ const labelFor = (o: { name: string; is_sandbox?: boolean }) =>
97
+ o.is_sandbox ? `${o.name} · SANDBOX` : o.name;
98
+
99
+ if (orgList.length <= 1) {
100
+ const only = orgList[0];
101
+ return (
102
+ <div style={{
103
+ padding: "0 14px 10px",
104
+ fontSize: "var(--bb-text-sm)",
105
+ fontWeight: 600,
106
+ color: "var(--bb-sidebar-text)",
107
+ display: "flex",
108
+ alignItems: "center",
109
+ gap: 8,
110
+ }}>
111
+ <span>{only?.name}</span>
112
+ {only?.is_sandbox && <SandboxPill />}
113
+ </div>
114
+ );
115
+ }
116
+
117
+ const currentOrg = orgList.find((o) => o.id === current);
118
+
119
+ return (
120
+ <div style={{ padding: "0 10px 10px" }}>
121
+ <select
122
+ disabled={disabled}
123
+ value={current}
124
+ onChange={(e) => onChange(e.target.value)}
125
+ style={{
126
+ width: "100%",
127
+ background: "rgba(255,255,255,0.06)",
128
+ border: currentOrg?.is_sandbox
129
+ ? "1px solid #f59e0b"
130
+ : "1px solid rgba(255,255,255,0.10)",
131
+ borderRadius: "var(--bb-radius)",
132
+ padding: "6px 10px",
133
+ fontSize: "var(--bb-text-sm)",
134
+ fontWeight: 600,
135
+ color: "var(--bb-sidebar-text)",
136
+ cursor: "pointer",
137
+ outline: "none",
138
+ }}
139
+ >
140
+ {orgs.map((o) => (
141
+ <option key={o.id} value={o.id}>{labelFor(o)}</option>
142
+ ))}
143
+ </select>
144
+ </div>
145
+ );
146
+ }
147
+
148
+ function SandboxPill() {
149
+ return (
150
+ <span style={{
151
+ fontSize: 9,
152
+ fontWeight: 700,
153
+ letterSpacing: 0.5,
154
+ color: "#fff",
155
+ background: "#f59e0b",
156
+ padding: "1px 5px",
157
+ borderRadius: 3,
158
+ }}>
159
+ SANDBOX
160
+ </span>
161
+ );
162
+ }