@meetreeve/ui 0.8.1 → 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.mjs CHANGED
@@ -1368,6 +1368,10 @@ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-run
1368
1368
  var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
1369
1369
  var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
1370
1370
  var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
1371
+ var DEFAULT_GRANTS_LOAD_ERROR = "Couldn't load sharing. Please try again.";
1372
+ var DEFAULT_GRANT_ERROR = "Couldn't share. Please try again.";
1373
+ var DEFAULT_REVOKE_ERROR = "Couldn't revoke access. Please try again.";
1374
+ var DEFAULT_ORG_DISABLED_REASON = "Org-wide connections aren't available to you.";
1371
1375
  var SKELETON_COUNT = 3;
1372
1376
  function capabilityLabel(capability) {
1373
1377
  if (capability === "read") return "Read only";
@@ -1382,6 +1386,17 @@ function formatSince(since) {
1382
1386
  function errorMessageFrom(err, fallback) {
1383
1387
  return err instanceof Error && err.message ? err.message : fallback;
1384
1388
  }
1389
+ function ownerLabel(entry) {
1390
+ const connection = entry.connection;
1391
+ if (connection.status !== "connected") return null;
1392
+ if (connection.kind === "org") return "Org";
1393
+ if (connection.owned_by_viewer) return "Yours";
1394
+ if (connection.shared_by) return `Shared by ${connection.shared_by.name}`;
1395
+ return null;
1396
+ }
1397
+ function isSharedWithViewer(entry) {
1398
+ return entry.connection.status === "connected" && Boolean(entry.connection.shared_by) && !entry.connection.owned_by_viewer;
1399
+ }
1385
1400
  function ConnectorPicker({
1386
1401
  client,
1387
1402
  purpose,
@@ -1393,6 +1408,7 @@ function ConnectorPicker({
1393
1408
  const [entries, setEntries] = useState7(null);
1394
1409
  const [loadError, setLoadError] = useState7(null);
1395
1410
  const [uiState, setUiState] = useState7({});
1411
+ const grantsSupported = typeof client.listGrants === "function" && typeof client.grant === "function" && typeof client.revokeGrant === "function";
1396
1412
  const clientRef = useRef5(client);
1397
1413
  clientRef.current = client;
1398
1414
  const purposeRef = useRef5(purpose);
@@ -1451,10 +1467,10 @@ function ConnectorPicker({
1451
1467
  };
1452
1468
  }, [gated, refreshAfterReturn]);
1453
1469
  const handleConnect = useCallback4(
1454
- async (entry) => {
1470
+ async (entry, kind) => {
1455
1471
  setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
1456
1472
  try {
1457
- const result = await clientRef.current.connect(entry);
1473
+ const result = await clientRef.current.connect(entry, { kind });
1458
1474
  if (result?.connect_url) {
1459
1475
  window.open(result.connect_url, "_blank", "noopener,noreferrer");
1460
1476
  return;
@@ -1494,121 +1510,429 @@ function ConnectorPicker({
1494
1510
  },
1495
1511
  [load]
1496
1512
  );
1513
+ const listGrants = useCallback4(
1514
+ (entry) => clientRef.current.listGrants?.(entry) ?? Promise.resolve([]),
1515
+ []
1516
+ );
1517
+ const addGrant = useCallback4(
1518
+ (entry, input) => clientRef.current.grant?.(entry, input) ?? Promise.resolve(),
1519
+ []
1520
+ );
1521
+ const revokeGrant = useCallback4(
1522
+ (entry, grantId) => clientRef.current.revokeGrant?.(entry, grantId) ?? Promise.resolve(),
1523
+ []
1524
+ );
1497
1525
  if (gated) return null;
1498
1526
  const containerClassName = cn(
1499
1527
  "flex flex-col",
1500
1528
  layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
1501
1529
  );
1502
- return /* @__PURE__ */ jsxs6("div", { "data-testid": "connector-picker", "data-layout": layout, className: containerClassName, children: [
1503
- entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ jsxs6(
1504
- "div",
1505
- {
1506
- "data-testid": "connector-picker-skeleton-item",
1507
- "aria-hidden": "true",
1508
- className: "flex items-center gap-3 rounded-lg border border-border p-3",
1509
- children: [
1510
- /* @__PURE__ */ jsx7("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
1511
- /* @__PURE__ */ jsx7("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
1512
- ]
1513
- },
1514
- i
1515
- )),
1516
- loadError && /* @__PURE__ */ jsxs6(
1517
- "div",
1518
- {
1519
- role: "alert",
1520
- "data-testid": "connector-picker-load-error",
1521
- className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
1522
- children: [
1523
- /* @__PURE__ */ jsx7("span", { children: loadError }),
1524
- /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
1525
- ]
1526
- }
1527
- ),
1528
- entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ jsx7("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
1529
- entries?.map((entry) => /* @__PURE__ */ jsx7(
1530
- ConnectorTile,
1531
- {
1532
- entry,
1533
- ui: uiState[entry.slug],
1534
- layout,
1535
- onConnect: handleConnect,
1536
- onDisconnect: handleDisconnect
1537
- },
1538
- entry.slug
1539
- ))
1530
+ const ownEntries = entries?.filter((entry) => !isSharedWithViewer(entry)) ?? null;
1531
+ const sharedEntries = entries?.filter(isSharedWithViewer) ?? [];
1532
+ return /* @__PURE__ */ jsxs6("div", { "data-testid": "connector-picker", "data-layout": layout, className: "flex flex-col gap-4", children: [
1533
+ /* @__PURE__ */ jsxs6("div", { className: containerClassName, children: [
1534
+ entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ jsxs6(
1535
+ "div",
1536
+ {
1537
+ "data-testid": "connector-picker-skeleton-item",
1538
+ "aria-hidden": "true",
1539
+ className: "flex items-center gap-3 rounded-lg border border-border p-3",
1540
+ children: [
1541
+ /* @__PURE__ */ jsx7("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
1542
+ /* @__PURE__ */ jsx7("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
1543
+ ]
1544
+ },
1545
+ i
1546
+ )),
1547
+ loadError && /* @__PURE__ */ jsxs6(
1548
+ "div",
1549
+ {
1550
+ role: "alert",
1551
+ "data-testid": "connector-picker-load-error",
1552
+ className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
1553
+ children: [
1554
+ /* @__PURE__ */ jsx7("span", { children: loadError }),
1555
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
1556
+ ]
1557
+ }
1558
+ ),
1559
+ entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ jsx7("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
1560
+ ownEntries?.map((entry) => /* @__PURE__ */ jsx7(
1561
+ ConnectorTile,
1562
+ {
1563
+ entry,
1564
+ ui: uiState[entry.slug],
1565
+ layout,
1566
+ grantsSupported,
1567
+ onConnect: handleConnect,
1568
+ onDisconnect: handleDisconnect,
1569
+ listGrants,
1570
+ addGrant,
1571
+ revokeGrant
1572
+ },
1573
+ entry.slug
1574
+ ))
1575
+ ] }),
1576
+ sharedEntries.length > 0 && /* @__PURE__ */ jsxs6("section", { "data-testid": "connector-picker-shared-with-me", className: "flex flex-col gap-2", children: [
1577
+ /* @__PURE__ */ jsx7("h3", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Shared with me" }),
1578
+ /* @__PURE__ */ jsx7("div", { className: containerClassName, children: sharedEntries.map((entry) => /* @__PURE__ */ jsx7(
1579
+ ConnectorTile,
1580
+ {
1581
+ entry,
1582
+ ui: uiState[entry.slug],
1583
+ layout,
1584
+ shared: true,
1585
+ grantsSupported,
1586
+ onConnect: handleConnect,
1587
+ onDisconnect: handleDisconnect,
1588
+ listGrants,
1589
+ addGrant,
1590
+ revokeGrant
1591
+ },
1592
+ entry.slug
1593
+ )) })
1594
+ ] })
1540
1595
  ] });
1541
1596
  }
1542
1597
  function ConnectorTile({
1543
1598
  entry,
1544
1599
  ui,
1545
1600
  layout,
1601
+ shared = false,
1602
+ grantsSupported,
1546
1603
  onConnect,
1547
- onDisconnect
1604
+ onDisconnect,
1605
+ listGrants,
1606
+ addGrant,
1607
+ revokeGrant
1548
1608
  }) {
1549
1609
  const isConnected = entry.connection.status === "connected";
1550
1610
  const isConnecting = ui?.kind === "connecting";
1551
1611
  const isDisconnecting = ui?.kind === "disconnecting";
1552
1612
  const errorMessage = ui?.kind === "error" ? ui.message : null;
1613
+ const [kind, setKind] = useState7("personal");
1614
+ const [sharingOpen, setSharingOpen] = useState7(false);
1615
+ const orgPermitted = entry.org_connect?.permitted === true;
1616
+ const orgDisabledReason = entry.org_connect?.reason || DEFAULT_ORG_DISABLED_REASON;
1617
+ const attribution = ownerLabel(entry);
1618
+ const canDisconnect = isConnected && !shared;
1619
+ const canManageSharing = isConnected && entry.connection.owned_by_viewer === true && grantsSupported;
1553
1620
  return /* @__PURE__ */ jsxs6(
1554
1621
  "div",
1555
1622
  {
1556
1623
  "data-testid": `connector-tile-${entry.slug}`,
1557
1624
  "data-connected": isConnected || void 0,
1558
1625
  "data-deprecated": entry.deprecated || void 0,
1559
- className: cn("flex items-center gap-3 p-3", layout === "tiles" && "rounded-lg border border-border"),
1626
+ className: cn("flex flex-col p-3", layout === "tiles" && "rounded-lg border border-border"),
1560
1627
  children: [
1561
- /* @__PURE__ */ jsx7(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
1562
- /* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
1563
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
1564
- /* @__PURE__ */ jsx7("p", { className: "truncate text-sm font-medium", children: entry.name }),
1565
- entry.deprecated && /* @__PURE__ */ jsx7(
1566
- "span",
1628
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
1629
+ /* @__PURE__ */ jsx7(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
1630
+ /* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
1631
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
1632
+ /* @__PURE__ */ jsx7("p", { className: "truncate text-sm font-medium", children: entry.name }),
1633
+ entry.deprecated && /* @__PURE__ */ jsx7(
1634
+ "span",
1635
+ {
1636
+ "data-testid": `connector-deprecated-${entry.slug}`,
1637
+ className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
1638
+ children: "No longer available"
1639
+ }
1640
+ )
1641
+ ] }),
1642
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1643
+ /* @__PURE__ */ jsx7("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
1644
+ entry.category && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1645
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xB7" }),
1646
+ /* @__PURE__ */ jsx7("span", { className: "truncate", children: entry.category })
1647
+ ] })
1648
+ ] }),
1649
+ isConnected && /* @__PURE__ */ jsx7(
1650
+ "p",
1567
1651
  {
1568
- "data-testid": `connector-deprecated-${entry.slug}`,
1569
- className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
1570
- children: "No longer available"
1652
+ "data-testid": `connector-connected-since-${entry.slug}`,
1653
+ className: "text-xs text-muted-foreground",
1654
+ children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
1571
1655
  }
1572
- )
1573
- ] }),
1574
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1575
- /* @__PURE__ */ jsx7("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
1576
- entry.category && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1577
- /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xB7" }),
1578
- /* @__PURE__ */ jsx7("span", { className: "truncate", children: entry.category })
1579
- ] })
1656
+ ),
1657
+ attribution && /* @__PURE__ */ jsx7(
1658
+ "p",
1659
+ {
1660
+ "data-testid": `connector-owner-${entry.slug}`,
1661
+ className: "text-xs text-muted-foreground",
1662
+ children: attribution
1663
+ }
1664
+ ),
1665
+ !isConnected && /* @__PURE__ */ jsxs6(
1666
+ "div",
1667
+ {
1668
+ "data-testid": `connector-kind-toggle-${entry.slug}`,
1669
+ role: "group",
1670
+ "aria-label": "Connect as",
1671
+ className: "mt-1 flex items-center gap-1",
1672
+ children: [
1673
+ /* @__PURE__ */ jsx7(
1674
+ "button",
1675
+ {
1676
+ type: "button",
1677
+ "data-testid": `connector-kind-personal-${entry.slug}`,
1678
+ "aria-pressed": kind === "personal",
1679
+ disabled: isConnecting,
1680
+ onClick: () => setKind("personal"),
1681
+ className: cn(
1682
+ "rounded-full border px-2 py-0.5 text-[11px]",
1683
+ kind === "personal" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
1684
+ "disabled:opacity-50"
1685
+ ),
1686
+ children: "Personal"
1687
+ }
1688
+ ),
1689
+ /* @__PURE__ */ jsxs6(
1690
+ "button",
1691
+ {
1692
+ type: "button",
1693
+ "data-testid": `connector-kind-org-${entry.slug}`,
1694
+ "aria-pressed": kind === "org",
1695
+ disabled: isConnecting || !orgPermitted,
1696
+ title: orgPermitted ? void 0 : orgDisabledReason,
1697
+ onClick: () => setKind("org"),
1698
+ className: cn(
1699
+ "rounded-full border px-2 py-0.5 text-[11px]",
1700
+ kind === "org" ? "border-primary bg-primary/10 font-medium" : "border-border text-muted-foreground",
1701
+ "disabled:opacity-50"
1702
+ ),
1703
+ children: [
1704
+ "Org",
1705
+ !orgPermitted && /* @__PURE__ */ jsxs6("span", { className: "sr-only", children: [
1706
+ " (",
1707
+ orgDisabledReason,
1708
+ ")"
1709
+ ] })
1710
+ ]
1711
+ }
1712
+ )
1713
+ ]
1714
+ }
1715
+ ),
1716
+ errorMessage && /* @__PURE__ */ jsx7("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
1580
1717
  ] }),
1581
- isConnected && /* @__PURE__ */ jsx7(
1582
- "p",
1718
+ isConnected ? /* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
1719
+ canDisconnect && /* @__PURE__ */ jsx7(
1720
+ "button",
1721
+ {
1722
+ type: "button",
1723
+ "data-testid": `connector-disconnect-${entry.slug}`,
1724
+ onClick: () => onDisconnect(entry),
1725
+ disabled: isDisconnecting,
1726
+ className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
1727
+ children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
1728
+ }
1729
+ ),
1730
+ canManageSharing && /* @__PURE__ */ jsx7(
1731
+ "button",
1732
+ {
1733
+ type: "button",
1734
+ "data-testid": `connector-manage-sharing-${entry.slug}`,
1735
+ "aria-expanded": sharingOpen,
1736
+ onClick: () => setSharingOpen((open) => !open),
1737
+ className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-muted-foreground hover:bg-foreground/10",
1738
+ children: sharingOpen ? "Hide sharing" : "Manage sharing"
1739
+ }
1740
+ )
1741
+ ] }) : /* @__PURE__ */ jsx7(
1742
+ "button",
1583
1743
  {
1584
- "data-testid": `connector-connected-since-${entry.slug}`,
1585
- className: "text-xs text-muted-foreground",
1586
- children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
1744
+ type: "button",
1745
+ "data-testid": `connector-connect-${entry.slug}`,
1746
+ onClick: () => onConnect(entry, kind),
1747
+ disabled: isConnecting,
1748
+ className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1749
+ children: isConnecting ? "Connecting\u2026" : "Connect"
1587
1750
  }
1588
- ),
1589
- errorMessage && /* @__PURE__ */ jsx7("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
1751
+ )
1590
1752
  ] }),
1591
- isConnected ? /* @__PURE__ */ jsx7(
1592
- "button",
1753
+ canManageSharing && sharingOpen && /* @__PURE__ */ jsx7(
1754
+ ManageSharing,
1593
1755
  {
1594
- type: "button",
1595
- "data-testid": `connector-disconnect-${entry.slug}`,
1596
- onClick: () => onDisconnect(entry),
1597
- disabled: isDisconnecting,
1598
- className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
1599
- children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
1756
+ entry,
1757
+ listGrants,
1758
+ addGrant,
1759
+ revokeGrant
1600
1760
  }
1601
- ) : /* @__PURE__ */ jsx7(
1602
- "button",
1761
+ )
1762
+ ]
1763
+ }
1764
+ );
1765
+ }
1766
+ function ManageSharing({
1767
+ entry,
1768
+ listGrants,
1769
+ addGrant,
1770
+ revokeGrant
1771
+ }) {
1772
+ const [grants, setGrants] = useState7(null);
1773
+ const [error, setError] = useState7(null);
1774
+ const [busy, setBusy] = useState7(false);
1775
+ const [granteeType, setGranteeType] = useState7("user");
1776
+ const [granteeId, setGranteeId] = useState7("");
1777
+ const roleOptions = entry.org_connect?.role_options ?? [];
1778
+ const entryRef = useRef5(entry);
1779
+ entryRef.current = entry;
1780
+ const load = useCallback4(async () => {
1781
+ try {
1782
+ setGrants(await listGrants(entryRef.current));
1783
+ setError(null);
1784
+ } catch (err) {
1785
+ setError(errorMessageFrom(err, DEFAULT_GRANTS_LOAD_ERROR));
1786
+ }
1787
+ }, [listGrants]);
1788
+ useEffect6(() => {
1789
+ void load();
1790
+ }, [load]);
1791
+ const handleAdd = useCallback4(async () => {
1792
+ const id = granteeId.trim();
1793
+ if (!id) return;
1794
+ setBusy(true);
1795
+ try {
1796
+ await addGrant(entryRef.current, { granteeType, granteeId: id });
1797
+ setGranteeId("");
1798
+ await load();
1799
+ } catch (err) {
1800
+ setError(errorMessageFrom(err, DEFAULT_GRANT_ERROR));
1801
+ } finally {
1802
+ setBusy(false);
1803
+ }
1804
+ }, [addGrant, granteeId, granteeType, load]);
1805
+ const handleRevoke = useCallback4(
1806
+ async (grantId) => {
1807
+ setBusy(true);
1808
+ try {
1809
+ await revokeGrant(entryRef.current, grantId);
1810
+ await load();
1811
+ } catch (err) {
1812
+ setError(errorMessageFrom(err, DEFAULT_REVOKE_ERROR));
1813
+ } finally {
1814
+ setBusy(false);
1815
+ }
1816
+ },
1817
+ [load, revokeGrant]
1818
+ );
1819
+ return /* @__PURE__ */ jsxs6(
1820
+ "div",
1821
+ {
1822
+ "data-testid": `connector-sharing-panel-${entry.slug}`,
1823
+ className: "mt-2 flex flex-col gap-2 rounded-md border border-border bg-foreground/5 p-2",
1824
+ children: [
1825
+ grants === null && !error && /* @__PURE__ */ jsx7(
1826
+ "p",
1603
1827
  {
1604
- type: "button",
1605
- "data-testid": `connector-connect-${entry.slug}`,
1606
- onClick: () => onConnect(entry),
1607
- disabled: isConnecting,
1608
- className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1609
- children: isConnecting ? "Connecting\u2026" : "Connect"
1828
+ "data-testid": `connector-grants-loading-${entry.slug}`,
1829
+ className: "text-xs text-muted-foreground",
1830
+ children: "Loading sharing\u2026"
1610
1831
  }
1611
- )
1832
+ ),
1833
+ error && /* @__PURE__ */ jsx7(
1834
+ "p",
1835
+ {
1836
+ role: "alert",
1837
+ "data-testid": `connector-sharing-error-${entry.slug}`,
1838
+ className: "text-xs text-destructive",
1839
+ children: error
1840
+ }
1841
+ ),
1842
+ grants !== null && grants.length === 0 && /* @__PURE__ */ jsx7(
1843
+ "p",
1844
+ {
1845
+ "data-testid": `connector-grants-empty-${entry.slug}`,
1846
+ className: "text-xs text-muted-foreground",
1847
+ children: "Not shared with anyone yet."
1848
+ }
1849
+ ),
1850
+ grants?.map((grant) => /* @__PURE__ */ jsxs6(
1851
+ "div",
1852
+ {
1853
+ "data-testid": `connector-grant-${entry.slug}-${grant.id}`,
1854
+ className: "flex items-center justify-between gap-2 text-xs",
1855
+ children: [
1856
+ /* @__PURE__ */ jsxs6("span", { className: "truncate", children: [
1857
+ grant.grantee_name || grant.grantee_id,
1858
+ /* @__PURE__ */ jsxs6("span", { className: "text-muted-foreground", children: [
1859
+ " ",
1860
+ "\xB7 ",
1861
+ grant.grantee_type === "role" ? "Role" : "User"
1862
+ ] })
1863
+ ] }),
1864
+ /* @__PURE__ */ jsx7(
1865
+ "button",
1866
+ {
1867
+ type: "button",
1868
+ "data-testid": `connector-grant-revoke-${entry.slug}-${grant.id}`,
1869
+ onClick: () => void handleRevoke(grant.id),
1870
+ disabled: busy,
1871
+ className: "shrink-0 rounded-md px-2 py-0.5 font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
1872
+ children: "Revoke"
1873
+ }
1874
+ )
1875
+ ]
1876
+ },
1877
+ grant.id
1878
+ )),
1879
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5", children: [
1880
+ /* @__PURE__ */ jsxs6(
1881
+ "select",
1882
+ {
1883
+ "data-testid": `connector-grant-type-${entry.slug}`,
1884
+ "aria-label": "Grantee type",
1885
+ value: granteeType,
1886
+ onChange: (event) => {
1887
+ setGranteeType(event.target.value);
1888
+ setGranteeId("");
1889
+ },
1890
+ disabled: busy,
1891
+ className: "rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
1892
+ children: [
1893
+ /* @__PURE__ */ jsx7("option", { value: "user", children: "User" }),
1894
+ /* @__PURE__ */ jsx7("option", { value: "role", children: "Role" })
1895
+ ]
1896
+ }
1897
+ ),
1898
+ granteeType === "user" ? /* @__PURE__ */ jsx7(
1899
+ "input",
1900
+ {
1901
+ "data-testid": `connector-grant-grantee-${entry.slug}`,
1902
+ "aria-label": "User ID",
1903
+ value: granteeId,
1904
+ onChange: (event) => setGranteeId(event.target.value),
1905
+ placeholder: "User ID",
1906
+ disabled: busy,
1907
+ className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50"
1908
+ }
1909
+ ) : /* @__PURE__ */ jsxs6(
1910
+ "select",
1911
+ {
1912
+ "data-testid": `connector-grant-role-${entry.slug}`,
1913
+ "aria-label": "Role",
1914
+ value: granteeId,
1915
+ onChange: (event) => setGranteeId(event.target.value),
1916
+ disabled: busy,
1917
+ className: "min-w-0 flex-1 rounded-md border border-border bg-transparent px-1.5 py-1 text-xs disabled:opacity-50",
1918
+ children: [
1919
+ /* @__PURE__ */ jsx7("option", { value: "", children: "Select a role\u2026" }),
1920
+ roleOptions.map((role) => /* @__PURE__ */ jsx7("option", { value: role.id, children: role.name }, role.id))
1921
+ ]
1922
+ }
1923
+ ),
1924
+ /* @__PURE__ */ jsx7(
1925
+ "button",
1926
+ {
1927
+ type: "button",
1928
+ "data-testid": `connector-grant-add-${entry.slug}`,
1929
+ onClick: () => void handleAdd(),
1930
+ disabled: busy || !granteeId.trim(),
1931
+ className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
1932
+ children: "Share"
1933
+ }
1934
+ )
1935
+ ] })
1612
1936
  ]
1613
1937
  }
1614
1938
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetreeve/ui",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "Shared brand-adaptive UI primitives for MindFortress frontends — min-size tokens, a favicon→logo→monogram BrandGlyph, a responsive header that keeps the search bar usable on mobile, and AppSidebar's viewport-driven auto-collapse ladder.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",