@meetreeve/ui 0.8.0 → 0.9.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/index.d.mts +76 -3
- package/dist/index.d.ts +76 -3
- package/dist/index.js +443 -99
- package/dist/index.mjs +443 -99
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -839,13 +839,23 @@ function Flyout({
|
|
|
839
839
|
className: cn(
|
|
840
840
|
"group flex w-full items-center gap-2.5 rounded-md px-3 py-1.5 text-sm transition-colors",
|
|
841
841
|
collapsed && "justify-center px-0",
|
|
842
|
-
|
|
842
|
+
// DEV-6586: this trigger renders as a row inside AppSidebar's own
|
|
843
|
+
// `<nav>` (a `bg-sidebar` surface that stays dark navy in both
|
|
844
|
+
// themes) — same fix as NavLink, same measured ratios (default
|
|
845
|
+
// 8.05:1, hover 13.65:1, active 10.95:1). The flyout PANEL below
|
|
846
|
+
// is intentionally left on document tokens: it renders via
|
|
847
|
+
// `position: fixed`, floating over the main content area on
|
|
848
|
+
// `bg-background`, not over the sidebar.
|
|
849
|
+
active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground/70 hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground"
|
|
843
850
|
),
|
|
844
851
|
children: [
|
|
845
852
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
846
853
|
Icon,
|
|
847
854
|
{
|
|
848
|
-
className: cn(
|
|
855
|
+
className: cn(
|
|
856
|
+
"size-4 shrink-0",
|
|
857
|
+
active ? "text-sidebar-accent-foreground" : "text-sidebar-foreground/70"
|
|
858
|
+
),
|
|
849
859
|
strokeWidth: 1.5,
|
|
850
860
|
"aria-hidden": "true"
|
|
851
861
|
}
|
|
@@ -928,13 +938,23 @@ function NavLink({
|
|
|
928
938
|
className: cn(
|
|
929
939
|
"group flex items-center gap-2.5 rounded-md px-3 py-1.5 text-sm transition-colors",
|
|
930
940
|
collapsed && "justify-center px-0",
|
|
931
|
-
|
|
941
|
+
// DEV-6586: the sidebar is a permanently dark-navy surface in BOTH
|
|
942
|
+
// app themes (see `--sidebar-*` in globals.css) — styling from
|
|
943
|
+
// document-theme tokens (text-foreground/text-muted-foreground) put
|
|
944
|
+
// dark-on-dark text here in light mode. `--sidebar-foreground` /
|
|
945
|
+
// `--sidebar-accent(-foreground)` are theme-invariant, so hover/
|
|
946
|
+
// active reliably INCREASE legibility instead of erasing it:
|
|
947
|
+
// default 8.05:1, hover 13.65:1, active 10.95:1 (all >= 4.5:1 AA).
|
|
948
|
+
active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground/70 hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground"
|
|
932
949
|
),
|
|
933
950
|
children: [
|
|
934
951
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
935
952
|
Icon,
|
|
936
953
|
{
|
|
937
|
-
className: cn(
|
|
954
|
+
className: cn(
|
|
955
|
+
"size-4 shrink-0",
|
|
956
|
+
active ? "text-sidebar-accent-foreground" : "text-sidebar-foreground/70"
|
|
957
|
+
),
|
|
938
958
|
strokeWidth: 1.5,
|
|
939
959
|
"aria-hidden": "true"
|
|
940
960
|
}
|
|
@@ -1077,7 +1097,7 @@ function AppSidebar({
|
|
|
1077
1097
|
href: brand.href,
|
|
1078
1098
|
"data-testid": "app-sidebar-brand",
|
|
1079
1099
|
title: brand.label,
|
|
1080
|
-
className: "flex items-center justify-center border-b border-border px-1 py-4",
|
|
1100
|
+
className: "flex items-center justify-center border-b border-sidebar-border px-1 py-4",
|
|
1081
1101
|
children: [
|
|
1082
1102
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "sr-only", children: brand.label }),
|
|
1083
1103
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BrandGlyph, { brand: { name: brand.label }, size: "xl" })
|
|
@@ -1090,9 +1110,9 @@ function AppSidebar({
|
|
|
1090
1110
|
{
|
|
1091
1111
|
href: brand.href,
|
|
1092
1112
|
"data-testid": "app-sidebar-brand",
|
|
1093
|
-
className: "flex items-center gap-2.5 border-b border-border px-5 py-4",
|
|
1113
|
+
className: "flex items-center gap-2.5 border-b border-sidebar-border px-5 py-4 text-sidebar-foreground",
|
|
1094
1114
|
children: [
|
|
1095
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "grid size-7 place-items-center rounded-md bg-primary text-primary-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-mono text-sm font-bold", children: (brand.label[0] ?? "r").toUpperCase() }) }),
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "grid size-7 place-items-center rounded-md bg-sidebar-primary text-sidebar-primary-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "font-mono text-sm font-bold", children: (brand.label[0] ?? "r").toUpperCase() }) }),
|
|
1096
1116
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate text-sm font-semibold tracking-tight", children: brand.label })
|
|
1097
1117
|
]
|
|
1098
1118
|
}
|
|
@@ -1148,7 +1168,7 @@ function AppSidebar({
|
|
|
1148
1168
|
"aria-modal": "true",
|
|
1149
1169
|
"aria-label": "Navigation",
|
|
1150
1170
|
"data-testid": "app-sidebar-mobile-drawer",
|
|
1151
|
-
className: "absolute inset-y-0 left-0 flex w-72 flex-col overflow-y-auto border-r border-border bg-sidebar",
|
|
1171
|
+
className: "absolute inset-y-0 left-0 flex w-72 flex-col overflow-y-auto border-r border-sidebar-border bg-sidebar",
|
|
1152
1172
|
children: [
|
|
1153
1173
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "sticky top-0 z-10 flex justify-end bg-sidebar p-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1154
1174
|
"button",
|
|
@@ -1159,7 +1179,7 @@ function AppSidebar({
|
|
|
1159
1179
|
"aria-label": `Close ${menuLabel.toLowerCase()}`,
|
|
1160
1180
|
onClick: () => setMobileOpen(false),
|
|
1161
1181
|
style: TOUCH_TARGET_STYLE,
|
|
1162
|
-
className: "flex items-center justify-center rounded-md p-2 text-foreground hover:bg-foreground/5",
|
|
1182
|
+
className: "flex items-center justify-center rounded-md p-2 text-sidebar-foreground/70 hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground",
|
|
1163
1183
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react2.X, { className: "h-5 w-5", "aria-hidden": "true" })
|
|
1164
1184
|
}
|
|
1165
1185
|
) }),
|
|
@@ -1177,7 +1197,7 @@ function AppSidebar({
|
|
|
1177
1197
|
{
|
|
1178
1198
|
"data-testid": "app-sidebar-desktop",
|
|
1179
1199
|
className: cn(
|
|
1180
|
-
"hidden shrink-0 flex-col border-r border-border bg-sidebar md:flex",
|
|
1200
|
+
"hidden shrink-0 flex-col border-r border-sidebar-border bg-sidebar md:flex",
|
|
1181
1201
|
collapsed ? COLLAPSED_WIDTH_CLASS : EXPANDED_WIDTH_CLASS,
|
|
1182
1202
|
// DEV-5331: a plain CSS width transition is enough — no new deps
|
|
1183
1203
|
// for a spring/JS animation. Respect prefers-reduced-motion.
|
|
@@ -1198,7 +1218,7 @@ function AppSidebar({
|
|
|
1198
1218
|
) })
|
|
1199
1219
|
}
|
|
1200
1220
|
),
|
|
1201
|
-
collapsible && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "border-t border-border px-2 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1221
|
+
collapsible && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "border-t border-sidebar-border px-2 py-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1202
1222
|
"button",
|
|
1203
1223
|
{
|
|
1204
1224
|
type: "button",
|
|
@@ -1208,7 +1228,7 @@ function AppSidebar({
|
|
|
1208
1228
|
title: collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
1209
1229
|
onClick: handleToggleCollapsed,
|
|
1210
1230
|
className: cn(
|
|
1211
|
-
"flex items-center gap-2.5 rounded-md px-3 py-1.5 text-sm text-
|
|
1231
|
+
"flex items-center gap-2.5 rounded-md px-3 py-1.5 text-sm text-sidebar-foreground/70 transition-colors hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground",
|
|
1212
1232
|
collapsed && "justify-center px-0"
|
|
1213
1233
|
),
|
|
1214
1234
|
children: [
|
|
@@ -1346,18 +1366,18 @@ function ContextSwitcher({
|
|
|
1346
1366
|
type: "button",
|
|
1347
1367
|
title: active?.name || placeholder,
|
|
1348
1368
|
"aria-label": active?.name || placeholder,
|
|
1349
|
-
className: "flex h-10 w-10 items-center justify-center rounded-lg text-
|
|
1369
|
+
className: "flex h-10 w-10 items-center justify-center rounded-lg text-sidebar-foreground/70 transition-colors hover:bg-sidebar-foreground/5 hover:text-sidebar-foreground",
|
|
1350
1370
|
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BrandGlyph, { brand: toBrand(active ?? { name: "" }), size: "lg" })
|
|
1351
1371
|
}
|
|
1352
1372
|
) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1353
1373
|
"button",
|
|
1354
1374
|
{
|
|
1355
1375
|
type: "button",
|
|
1356
|
-
className: "group flex items-center gap-2 rounded-md border border-border bg-
|
|
1376
|
+
className: "group flex items-center gap-2 rounded-md border border-sidebar-border bg-sidebar-accent/60 px-2.5 py-1.5 text-sm font-medium transition-colors hover:bg-sidebar",
|
|
1357
1377
|
children: [
|
|
1358
1378
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BrandGlyph, { brand: toBrand(active ?? { name: "" }) }),
|
|
1359
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "max-w-[140px] truncate text-foreground
|
|
1360
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronDown, { className: "h-3.5 w-3.5 text-
|
|
1379
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "max-w-[140px] truncate text-sidebar-accent-foreground", children: active?.name || placeholder }),
|
|
1380
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronDown, { className: "h-3.5 w-3.5 text-sidebar-foreground/70 transition-transform group-data-[state=open]:rotate-180" })
|
|
1361
1381
|
]
|
|
1362
1382
|
}
|
|
1363
1383
|
) }),
|
|
@@ -1403,6 +1423,10 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
|
1403
1423
|
var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
|
|
1404
1424
|
var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
|
|
1405
1425
|
var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
|
|
1426
|
+
var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
|
|
1427
|
+
var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
|
|
1428
|
+
var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
|
|
1429
|
+
var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
|
|
1406
1430
|
var SKELETON_COUNT = 3;
|
|
1407
1431
|
function capabilityLabel(capability) {
|
|
1408
1432
|
if (capability === "read") return "Read only";
|
|
@@ -1417,6 +1441,17 @@ function formatSince(since) {
|
|
|
1417
1441
|
function errorMessageFrom(err, fallback) {
|
|
1418
1442
|
return err instanceof Error && err.message ? err.message : fallback;
|
|
1419
1443
|
}
|
|
1444
|
+
function ownerLabel(entry) {
|
|
1445
|
+
const connection = entry.connection;
|
|
1446
|
+
if (connection.status !== "connected") return null;
|
|
1447
|
+
if (connection.kind === "org") return "Org";
|
|
1448
|
+
if (connection.owned_by_viewer) return "Yours";
|
|
1449
|
+
if (connection.shared_by) return `Shared by ${connection.shared_by.name}`;
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
function isSharedWithViewer(entry) {
|
|
1453
|
+
return entry.connection.status === "connected" && Boolean(entry.connection.shared_by) && !entry.connection.owned_by_viewer;
|
|
1454
|
+
}
|
|
1420
1455
|
function ConnectorPicker({
|
|
1421
1456
|
client,
|
|
1422
1457
|
purpose,
|
|
@@ -1428,6 +1463,7 @@ function ConnectorPicker({
|
|
|
1428
1463
|
const [entries, setEntries] = (0, import_react9.useState)(null);
|
|
1429
1464
|
const [loadError, setLoadError] = (0, import_react9.useState)(null);
|
|
1430
1465
|
const [uiState, setUiState] = (0, import_react9.useState)({});
|
|
1466
|
+
const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
|
|
1431
1467
|
const clientRef = (0, import_react9.useRef)(client);
|
|
1432
1468
|
clientRef.current = client;
|
|
1433
1469
|
const purposeRef = (0, import_react9.useRef)(purpose);
|
|
@@ -1486,10 +1522,10 @@ function ConnectorPicker({
|
|
|
1486
1522
|
};
|
|
1487
1523
|
}, [gated, refreshAfterReturn]);
|
|
1488
1524
|
const handleConnect = (0, import_react9.useCallback)(
|
|
1489
|
-
async (entry) => {
|
|
1525
|
+
async (entry, kind) => {
|
|
1490
1526
|
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1491
1527
|
try {
|
|
1492
|
-
const result = await clientRef.current.connect(entry);
|
|
1528
|
+
const result = await clientRef.current.connect(entry, { kind });
|
|
1493
1529
|
if (result?.connect_url) {
|
|
1494
1530
|
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1495
1531
|
return;
|
|
@@ -1529,121 +1565,429 @@ function ConnectorPicker({
|
|
|
1529
1565
|
},
|
|
1530
1566
|
[load]
|
|
1531
1567
|
);
|
|
1568
|
+
const listGrants = (0, import_react9.useCallback)(
|
|
1569
|
+
(entry) => clientRef.current.listGrants?.(entry) ?? Promise.resolve([]),
|
|
1570
|
+
[]
|
|
1571
|
+
);
|
|
1572
|
+
const addGrant = (0, import_react9.useCallback)(
|
|
1573
|
+
(entry, input) => clientRef.current.grant?.(entry, input) ?? Promise.resolve(),
|
|
1574
|
+
[]
|
|
1575
|
+
);
|
|
1576
|
+
const revokeGrant = (0, import_react9.useCallback)(
|
|
1577
|
+
(entry, grantId) => clientRef.current.revokeGrant?.(entry, grantId) ?? Promise.resolve(),
|
|
1578
|
+
[]
|
|
1579
|
+
);
|
|
1532
1580
|
if (gated) return null;
|
|
1533
1581
|
const containerClassName = cn(
|
|
1534
1582
|
"flex flex-col",
|
|
1535
1583
|
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1536
1584
|
);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
"
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
"
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
{
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1585
|
+
const ownEntries = entries?.filter((entry) => !isSharedWithViewer(entry)) ?? null;
|
|
1586
|
+
const sharedEntries = entries?.filter(isSharedWithViewer) ?? [];
|
|
1587
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "connector-picker", "data-layout": layout, className: "flex flex-col gap-4", children: [
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: containerClassName, children: [
|
|
1589
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1590
|
+
"div",
|
|
1591
|
+
{
|
|
1592
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1593
|
+
"aria-hidden": "true",
|
|
1594
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1595
|
+
children: [
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1597
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1598
|
+
]
|
|
1599
|
+
},
|
|
1600
|
+
i
|
|
1601
|
+
)),
|
|
1602
|
+
loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1603
|
+
"div",
|
|
1604
|
+
{
|
|
1605
|
+
role: "alert",
|
|
1606
|
+
"data-testid": "connector-picker-load-error",
|
|
1607
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1608
|
+
children: [
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: loadError }),
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1611
|
+
]
|
|
1612
|
+
}
|
|
1613
|
+
),
|
|
1614
|
+
entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
|
|
1615
|
+
ownEntries?.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1616
|
+
ConnectorTile,
|
|
1617
|
+
{
|
|
1618
|
+
entry,
|
|
1619
|
+
ui: uiState[entry.slug],
|
|
1620
|
+
layout,
|
|
1621
|
+
grantsSupported,
|
|
1622
|
+
onConnect: handleConnect,
|
|
1623
|
+
onDisconnect: handleDisconnect,
|
|
1624
|
+
listGrants,
|
|
1625
|
+
addGrant,
|
|
1626
|
+
revokeGrant
|
|
1627
|
+
},
|
|
1628
|
+
entry.slug
|
|
1629
|
+
))
|
|
1630
|
+
] }),
|
|
1631
|
+
sharedEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "data-testid": "connector-picker-shared-with-me", className: "flex flex-col gap-2", children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Shared with me" }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: containerClassName, children: sharedEntries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1634
|
+
ConnectorTile,
|
|
1635
|
+
{
|
|
1636
|
+
entry,
|
|
1637
|
+
ui: uiState[entry.slug],
|
|
1638
|
+
layout,
|
|
1639
|
+
shared: true,
|
|
1640
|
+
grantsSupported,
|
|
1641
|
+
onConnect: handleConnect,
|
|
1642
|
+
onDisconnect: handleDisconnect,
|
|
1643
|
+
listGrants,
|
|
1644
|
+
addGrant,
|
|
1645
|
+
revokeGrant
|
|
1646
|
+
},
|
|
1647
|
+
entry.slug
|
|
1648
|
+
)) })
|
|
1649
|
+
] })
|
|
1575
1650
|
] });
|
|
1576
1651
|
}
|
|
1577
1652
|
function ConnectorTile({
|
|
1578
1653
|
entry,
|
|
1579
1654
|
ui,
|
|
1580
1655
|
layout,
|
|
1656
|
+
shared = false,
|
|
1657
|
+
grantsSupported,
|
|
1581
1658
|
onConnect,
|
|
1582
|
-
onDisconnect
|
|
1659
|
+
onDisconnect,
|
|
1660
|
+
listGrants,
|
|
1661
|
+
addGrant,
|
|
1662
|
+
revokeGrant
|
|
1583
1663
|
}) {
|
|
1584
1664
|
const isConnected = entry.connection.status === "connected";
|
|
1585
1665
|
const isConnecting = ui?.kind === "connecting";
|
|
1586
1666
|
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1587
1667
|
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1668
|
+
const [kind, setKind] = (0, import_react9.useState)("personal");
|
|
1669
|
+
const [sharingOpen, setSharingOpen] = (0, import_react9.useState)(false);
|
|
1670
|
+
const orgPermitted = entry.org_connect?.permitted === true;
|
|
1671
|
+
const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
|
|
1672
|
+
const attribution = ownerLabel(entry);
|
|
1673
|
+
const canDisconnect = isConnected && !shared;
|
|
1674
|
+
const canManageSharing = isConnected && entry.connection.owned_by_viewer === true && grantsSupported;
|
|
1588
1675
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1589
1676
|
"div",
|
|
1590
1677
|
{
|
|
1591
1678
|
"data-testid": `connector-tile-${entry.slug}`,
|
|
1592
1679
|
"data-connected": isConnected || void 0,
|
|
1593
1680
|
"data-deprecated": entry.deprecated || void 0,
|
|
1594
|
-
className: cn("flex
|
|
1681
|
+
className: cn("flex flex-col p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1595
1682
|
children: [
|
|
1596
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.
|
|
1597
|
-
|
|
1598
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "
|
|
1599
|
-
/* @__PURE__ */ (0, import_jsx_runtime7.
|
|
1600
|
-
|
|
1601
|
-
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1685
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1686
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1687
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1688
|
+
entry.deprecated && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1689
|
+
"span",
|
|
1690
|
+
{
|
|
1691
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1692
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1693
|
+
children: "No longer available"
|
|
1694
|
+
}
|
|
1695
|
+
)
|
|
1696
|
+
] }),
|
|
1697
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1699
|
+
entry.category && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: entry.category })
|
|
1702
|
+
] })
|
|
1703
|
+
] }),
|
|
1704
|
+
isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1705
|
+
"p",
|
|
1602
1706
|
{
|
|
1603
|
-
"data-testid": `connector-
|
|
1604
|
-
className: "
|
|
1605
|
-
children:
|
|
1707
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1708
|
+
className: "text-xs text-muted-foreground",
|
|
1709
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1606
1710
|
}
|
|
1607
|
-
)
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1711
|
+
),
|
|
1712
|
+
attribution && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1713
|
+
"p",
|
|
1714
|
+
{
|
|
1715
|
+
"data-testid": `connector-owner-${entry.slug}`,
|
|
1716
|
+
className: "text-xs text-muted-foreground",
|
|
1717
|
+
children: attribution
|
|
1718
|
+
}
|
|
1719
|
+
),
|
|
1720
|
+
!isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1721
|
+
"div",
|
|
1722
|
+
{
|
|
1723
|
+
"data-testid": `connector-kind-toggle-${entry.slug}`,
|
|
1724
|
+
role: "group",
|
|
1725
|
+
"aria-label": "Connect as",
|
|
1726
|
+
className: "mt-1 flex items-center gap-1",
|
|
1727
|
+
children: [
|
|
1728
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1729
|
+
"button",
|
|
1730
|
+
{
|
|
1731
|
+
type: "button",
|
|
1732
|
+
"data-testid": `connector-kind-personal-${entry.slug}`,
|
|
1733
|
+
"aria-pressed": kind === "personal",
|
|
1734
|
+
disabled: isConnecting,
|
|
1735
|
+
onClick: () => setKind("personal"),
|
|
1736
|
+
className: cn(
|
|
1737
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1738
|
+
kind === "personal" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1739
|
+
"disabled:opacity-50"
|
|
1740
|
+
),
|
|
1741
|
+
children: "Personal"
|
|
1742
|
+
}
|
|
1743
|
+
),
|
|
1744
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1745
|
+
"button",
|
|
1746
|
+
{
|
|
1747
|
+
type: "button",
|
|
1748
|
+
"data-testid": `connector-kind-org-${entry.slug}`,
|
|
1749
|
+
"aria-pressed": kind === "org",
|
|
1750
|
+
disabled: isConnecting || !orgPermitted,
|
|
1751
|
+
title: orgPermitted ? void 0 : orgDisabledReason,
|
|
1752
|
+
onClick: () => setKind("org"),
|
|
1753
|
+
className: cn(
|
|
1754
|
+
"rounded-full border px-2 py-0.5 text-[11px]",
|
|
1755
|
+
kind === "org" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
|
|
1756
|
+
"disabled:opacity-50"
|
|
1757
|
+
),
|
|
1758
|
+
children: [
|
|
1759
|
+
"Org",
|
|
1760
|
+
!orgPermitted && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "sr-only", children: [
|
|
1761
|
+
" (",
|
|
1762
|
+
orgDisabledReason,
|
|
1763
|
+
")"
|
|
1764
|
+
] })
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
)
|
|
1768
|
+
]
|
|
1769
|
+
}
|
|
1770
|
+
),
|
|
1771
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1615
1772
|
] }),
|
|
1616
|
-
isConnected
|
|
1617
|
-
|
|
1773
|
+
isConnected ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
1774
|
+
canDisconnect && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1775
|
+
"button",
|
|
1776
|
+
{
|
|
1777
|
+
type: "button",
|
|
1778
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1779
|
+
onClick: () => onDisconnect(entry),
|
|
1780
|
+
disabled: isDisconnecting,
|
|
1781
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1782
|
+
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
canManageSharing && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1786
|
+
"button",
|
|
1787
|
+
{
|
|
1788
|
+
type: "button",
|
|
1789
|
+
"data-testid": `connector-manage-sharing-${entry.slug}`,
|
|
1790
|
+
"aria-expanded": sharingOpen,
|
|
1791
|
+
onClick: () => setSharingOpen((open) => !open),
|
|
1792
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
|
|
1793
|
+
children: sharingOpen ? "Hide sharing" : "Manage sharing"
|
|
1794
|
+
}
|
|
1795
|
+
)
|
|
1796
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1797
|
+
"button",
|
|
1618
1798
|
{
|
|
1619
|
-
"
|
|
1620
|
-
|
|
1621
|
-
|
|
1799
|
+
type: "button",
|
|
1800
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1801
|
+
onClick: () => onConnect(entry, kind),
|
|
1802
|
+
disabled: isConnecting,
|
|
1803
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1804
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1622
1805
|
}
|
|
1623
|
-
)
|
|
1624
|
-
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1806
|
+
)
|
|
1625
1807
|
] }),
|
|
1626
|
-
|
|
1627
|
-
|
|
1808
|
+
canManageSharing && sharingOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1809
|
+
ManageSharing,
|
|
1628
1810
|
{
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1634
|
-
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1811
|
+
entry,
|
|
1812
|
+
listGrants,
|
|
1813
|
+
addGrant,
|
|
1814
|
+
revokeGrant
|
|
1635
1815
|
}
|
|
1636
|
-
)
|
|
1637
|
-
|
|
1816
|
+
)
|
|
1817
|
+
]
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
function ManageSharing({
|
|
1822
|
+
entry,
|
|
1823
|
+
listGrants,
|
|
1824
|
+
addGrant,
|
|
1825
|
+
revokeGrant
|
|
1826
|
+
}) {
|
|
1827
|
+
const [grants, setGrants] = (0, import_react9.useState)(null);
|
|
1828
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1829
|
+
const [busy, setBusy] = (0, import_react9.useState)(false);
|
|
1830
|
+
const [granteeType, setGranteeType] = (0, import_react9.useState)("user");
|
|
1831
|
+
const [granteeId, setGranteeId] = (0, import_react9.useState)("");
|
|
1832
|
+
const roleOptions = entry.org_connect?.role_options ?? [];
|
|
1833
|
+
const entryRef = (0, import_react9.useRef)(entry);
|
|
1834
|
+
entryRef.current = entry;
|
|
1835
|
+
const load = (0, import_react9.useCallback)(async () => {
|
|
1836
|
+
try {
|
|
1837
|
+
setGrants(await listGrants(entryRef.current));
|
|
1838
|
+
setError(null);
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
setError(errorMessageFrom(err, DEFAULT_GRANTS_LOAD_ERROR));
|
|
1841
|
+
}
|
|
1842
|
+
}, [listGrants]);
|
|
1843
|
+
(0, import_react9.useEffect)(() => {
|
|
1844
|
+
void load();
|
|
1845
|
+
}, [load]);
|
|
1846
|
+
const handleAdd = (0, import_react9.useCallback)(async () => {
|
|
1847
|
+
const id = granteeId.trim();
|
|
1848
|
+
if (!id) return;
|
|
1849
|
+
setBusy(true);
|
|
1850
|
+
try {
|
|
1851
|
+
await addGrant(entryRef.current, { granteeType, granteeId: id });
|
|
1852
|
+
setGranteeId("");
|
|
1853
|
+
await load();
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
setError(errorMessageFrom(err, DEFAULT_GRANT_ERROR));
|
|
1856
|
+
} finally {
|
|
1857
|
+
setBusy(false);
|
|
1858
|
+
}
|
|
1859
|
+
}, [addGrant, granteeId, granteeType, load]);
|
|
1860
|
+
const handleRevoke = (0, import_react9.useCallback)(
|
|
1861
|
+
async (grantId) => {
|
|
1862
|
+
setBusy(true);
|
|
1863
|
+
try {
|
|
1864
|
+
await revokeGrant(entryRef.current, grantId);
|
|
1865
|
+
await load();
|
|
1866
|
+
} catch (err) {
|
|
1867
|
+
setError(errorMessageFrom(err, DEFAULT_REVOKE_ERROR));
|
|
1868
|
+
} finally {
|
|
1869
|
+
setBusy(false);
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
[load, revokeGrant]
|
|
1873
|
+
);
|
|
1874
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1875
|
+
"div",
|
|
1876
|
+
{
|
|
1877
|
+
"data-testid": `connector-sharing-panel-${entry.slug}`,
|
|
1878
|
+
className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-foreground/5 p-2",
|
|
1879
|
+
children: [
|
|
1880
|
+
grants === null && !error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1881
|
+
"p",
|
|
1638
1882
|
{
|
|
1639
|
-
|
|
1640
|
-
"
|
|
1641
|
-
|
|
1642
|
-
disabled: isConnecting,
|
|
1643
|
-
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1644
|
-
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1883
|
+
"data-testid": `connector-grants-loading-${entry.slug}`,
|
|
1884
|
+
className: "text-xs text-muted-foreground",
|
|
1885
|
+
children: "Loading sharing\u2026"
|
|
1645
1886
|
}
|
|
1646
|
-
)
|
|
1887
|
+
),
|
|
1888
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1889
|
+
"p",
|
|
1890
|
+
{
|
|
1891
|
+
role: "alert",
|
|
1892
|
+
"data-testid": `connector-sharing-error-${entry.slug}`,
|
|
1893
|
+
className: "text-xs text-destructive",
|
|
1894
|
+
children: error
|
|
1895
|
+
}
|
|
1896
|
+
),
|
|
1897
|
+
grants !== null && grants.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1898
|
+
"p",
|
|
1899
|
+
{
|
|
1900
|
+
"data-testid": `connector-grants-empty-${entry.slug}`,
|
|
1901
|
+
className: "text-xs text-muted-foreground",
|
|
1902
|
+
children: "Not shared with anyone yet."
|
|
1903
|
+
}
|
|
1904
|
+
),
|
|
1905
|
+
grants?.map((grant) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1906
|
+
"div",
|
|
1907
|
+
{
|
|
1908
|
+
"data-testid": `connector-grant-${entry.slug}-${grant.id}`,
|
|
1909
|
+
className: "flex items-center justify-between gap-2 text-xs",
|
|
1910
|
+
children: [
|
|
1911
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "truncate", children: [
|
|
1912
|
+
grant.grantee_name || grant.grantee_id,
|
|
1913
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
1914
|
+
" ",
|
|
1915
|
+
"\xB7 ",
|
|
1916
|
+
grant.grantee_type === "role" ? "Role" : "User"
|
|
1917
|
+
] })
|
|
1918
|
+
] }),
|
|
1919
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1920
|
+
"button",
|
|
1921
|
+
{
|
|
1922
|
+
type: "button",
|
|
1923
|
+
"data-testid": `connector-grant-revoke-${entry.slug}-${grant.id}`,
|
|
1924
|
+
onClick: () => void handleRevoke(grant.id),
|
|
1925
|
+
disabled: busy,
|
|
1926
|
+
className: "shrink-0 rounded-md px-2 py-0.5 font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1927
|
+
children: "Revoke"
|
|
1928
|
+
}
|
|
1929
|
+
)
|
|
1930
|
+
]
|
|
1931
|
+
},
|
|
1932
|
+
grant.id
|
|
1933
|
+
)),
|
|
1934
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
1935
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1936
|
+
"select",
|
|
1937
|
+
{
|
|
1938
|
+
"data-testid": `connector-grant-type-${entry.slug}`,
|
|
1939
|
+
"aria-label": "Grantee type",
|
|
1940
|
+
value: granteeType,
|
|
1941
|
+
onChange: (event) => {
|
|
1942
|
+
setGranteeType(event.target.value);
|
|
1943
|
+
setGranteeId("");
|
|
1944
|
+
},
|
|
1945
|
+
disabled: busy,
|
|
1946
|
+
className: "rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1947
|
+
children: [
|
|
1948
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "user", children: "User" }),
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "role", children: "Role" })
|
|
1950
|
+
]
|
|
1951
|
+
}
|
|
1952
|
+
),
|
|
1953
|
+
granteeType === "user" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1954
|
+
"input",
|
|
1955
|
+
{
|
|
1956
|
+
"data-testid": `connector-grant-grantee-${entry.slug}`,
|
|
1957
|
+
"aria-label": "User ID",
|
|
1958
|
+
value: granteeId,
|
|
1959
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1960
|
+
placeholder: "User ID",
|
|
1961
|
+
disabled: busy,
|
|
1962
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50"
|
|
1963
|
+
}
|
|
1964
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1965
|
+
"select",
|
|
1966
|
+
{
|
|
1967
|
+
"data-testid": `connector-grant-role-${entry.slug}`,
|
|
1968
|
+
"aria-label": "Role",
|
|
1969
|
+
value: granteeId,
|
|
1970
|
+
onChange: (event) => setGranteeId(event.target.value),
|
|
1971
|
+
disabled: busy,
|
|
1972
|
+
className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
|
|
1973
|
+
children: [
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "Select a role\u2026" }),
|
|
1975
|
+
roleOptions.map((role) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: role.id, children: role.name }, role.id))
|
|
1976
|
+
]
|
|
1977
|
+
}
|
|
1978
|
+
),
|
|
1979
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1980
|
+
"button",
|
|
1981
|
+
{
|
|
1982
|
+
type: "button",
|
|
1983
|
+
"data-testid": `connector-grant-add-${entry.slug}`,
|
|
1984
|
+
onClick: () => void handleAdd(),
|
|
1985
|
+
disabled: busy || !granteeId.trim(),
|
|
1986
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1987
|
+
children: "Share"
|
|
1988
|
+
}
|
|
1989
|
+
)
|
|
1990
|
+
] })
|
|
1647
1991
|
]
|
|
1648
1992
|
}
|
|
1649
1993
|
);
|