@meetreeve/ui 0.7.1 → 0.8.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 +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +256 -2
- package/dist/index.mjs +255 -2
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -584,4 +584,71 @@ interface ContextSwitcherProps {
|
|
|
584
584
|
}
|
|
585
585
|
declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
|
|
586
586
|
|
|
587
|
-
|
|
587
|
+
/**
|
|
588
|
+
* ConnectorPicker (DEV-5578 / CONN-3) — the one data-driven connect
|
|
589
|
+
* component in @meetreeve/ui. Renders a catalog of connectable entries from
|
|
590
|
+
* an injected client, owning the whole per-entry state machine
|
|
591
|
+
* (not-connected -> connecting -> connected, disconnect, inline per-tile
|
|
592
|
+
* errors, and a return-from-OAuth refresh). Every label is derived from the
|
|
593
|
+
* entry data the client returns — this file must never hardcode a specific
|
|
594
|
+
* provider's name (an explicit acceptance criterion); a host app supplies
|
|
595
|
+
* that entirely through `ConnectableEntry.name` / `.logo` / `.category`.
|
|
596
|
+
*
|
|
597
|
+
* Transport-injection precedent: `linkComponent` (DEV-4429/DEV-4391) —
|
|
598
|
+
* inject the transport, keep behavior in the package. Here the "transport"
|
|
599
|
+
* is the whole backend round-trip (list/connect/disconnect), injected as
|
|
600
|
+
* `client` (Matt's decision, 2026-07-25): Reeve passes one built on its own
|
|
601
|
+
* fetch client; a tenant clone passes ~10 lines of its own.
|
|
602
|
+
*/
|
|
603
|
+
type ConnectableTransport = "composio" | "native";
|
|
604
|
+
type ConnectableCapability = "read" | "write" | "readwrite";
|
|
605
|
+
interface ConnectableEntry {
|
|
606
|
+
slug: string;
|
|
607
|
+
name: string;
|
|
608
|
+
logo: string | null;
|
|
609
|
+
category: string | null;
|
|
610
|
+
transport: ConnectableTransport;
|
|
611
|
+
capability: ConnectableCapability;
|
|
612
|
+
purposes: string[];
|
|
613
|
+
connection: {
|
|
614
|
+
status: "connected" | "not_connected";
|
|
615
|
+
connected_account_id: string | null;
|
|
616
|
+
since: string | null;
|
|
617
|
+
};
|
|
618
|
+
/** true = tenant has a live connection but the entry left the allowlist. */
|
|
619
|
+
deprecated: boolean;
|
|
620
|
+
}
|
|
621
|
+
interface ConnectorPickerClient {
|
|
622
|
+
listConnectable(opts: {
|
|
623
|
+
purpose?: string;
|
|
624
|
+
}): Promise<ConnectableEntry[]>;
|
|
625
|
+
/** composio -> open the returned connect_url; native may resolve with none. */
|
|
626
|
+
connect(entry: ConnectableEntry): Promise<{
|
|
627
|
+
connect_url?: string;
|
|
628
|
+
}>;
|
|
629
|
+
disconnect(entry: ConnectableEntry): Promise<void>;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Structurally identical to the app's own `DnaLens` (src/lib/dna-lens.ts) —
|
|
633
|
+
* this package must never import app code, so it declares its own copy
|
|
634
|
+
* rather than sharing the type.
|
|
635
|
+
*/
|
|
636
|
+
type ConnectorPickerLens = "visitor" | "member" | "owner";
|
|
637
|
+
interface ConnectorPickerProps {
|
|
638
|
+
/** The injected transport adapter — required. */
|
|
639
|
+
client: ConnectorPickerClient;
|
|
640
|
+
/** Optional purpose slice, forwarded to `client.listConnectable`. */
|
|
641
|
+
purpose?: string;
|
|
642
|
+
/**
|
|
643
|
+
* Default "owner". "visitor"/"member" render nothing and make ZERO
|
|
644
|
+
* client calls — some host surfaces (e.g. a structurally owner-only
|
|
645
|
+
* knowledge page) have no visitor/member concept at all, and forcing them
|
|
646
|
+
* to synthesize a lens would import dead branches.
|
|
647
|
+
*/
|
|
648
|
+
lens?: ConnectorPickerLens;
|
|
649
|
+
layout?: "tiles" | "list";
|
|
650
|
+
onConnected?: (entry: ConnectableEntry) => void;
|
|
651
|
+
}
|
|
652
|
+
declare function ConnectorPicker({ client, purpose, lens, layout, onConnected, }: ConnectorPickerProps): react.JSX.Element | null;
|
|
653
|
+
|
|
654
|
+
export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ConnectableCapability, type ConnectableEntry, type ConnectableTransport, ConnectorPicker, type ConnectorPickerClient, type ConnectorPickerLens, type ConnectorPickerProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
|
package/dist/index.d.ts
CHANGED
|
@@ -584,4 +584,71 @@ interface ContextSwitcherProps {
|
|
|
584
584
|
}
|
|
585
585
|
declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
|
|
586
586
|
|
|
587
|
-
|
|
587
|
+
/**
|
|
588
|
+
* ConnectorPicker (DEV-5578 / CONN-3) — the one data-driven connect
|
|
589
|
+
* component in @meetreeve/ui. Renders a catalog of connectable entries from
|
|
590
|
+
* an injected client, owning the whole per-entry state machine
|
|
591
|
+
* (not-connected -> connecting -> connected, disconnect, inline per-tile
|
|
592
|
+
* errors, and a return-from-OAuth refresh). Every label is derived from the
|
|
593
|
+
* entry data the client returns — this file must never hardcode a specific
|
|
594
|
+
* provider's name (an explicit acceptance criterion); a host app supplies
|
|
595
|
+
* that entirely through `ConnectableEntry.name` / `.logo` / `.category`.
|
|
596
|
+
*
|
|
597
|
+
* Transport-injection precedent: `linkComponent` (DEV-4429/DEV-4391) —
|
|
598
|
+
* inject the transport, keep behavior in the package. Here the "transport"
|
|
599
|
+
* is the whole backend round-trip (list/connect/disconnect), injected as
|
|
600
|
+
* `client` (Matt's decision, 2026-07-25): Reeve passes one built on its own
|
|
601
|
+
* fetch client; a tenant clone passes ~10 lines of its own.
|
|
602
|
+
*/
|
|
603
|
+
type ConnectableTransport = "composio" | "native";
|
|
604
|
+
type ConnectableCapability = "read" | "write" | "readwrite";
|
|
605
|
+
interface ConnectableEntry {
|
|
606
|
+
slug: string;
|
|
607
|
+
name: string;
|
|
608
|
+
logo: string | null;
|
|
609
|
+
category: string | null;
|
|
610
|
+
transport: ConnectableTransport;
|
|
611
|
+
capability: ConnectableCapability;
|
|
612
|
+
purposes: string[];
|
|
613
|
+
connection: {
|
|
614
|
+
status: "connected" | "not_connected";
|
|
615
|
+
connected_account_id: string | null;
|
|
616
|
+
since: string | null;
|
|
617
|
+
};
|
|
618
|
+
/** true = tenant has a live connection but the entry left the allowlist. */
|
|
619
|
+
deprecated: boolean;
|
|
620
|
+
}
|
|
621
|
+
interface ConnectorPickerClient {
|
|
622
|
+
listConnectable(opts: {
|
|
623
|
+
purpose?: string;
|
|
624
|
+
}): Promise<ConnectableEntry[]>;
|
|
625
|
+
/** composio -> open the returned connect_url; native may resolve with none. */
|
|
626
|
+
connect(entry: ConnectableEntry): Promise<{
|
|
627
|
+
connect_url?: string;
|
|
628
|
+
}>;
|
|
629
|
+
disconnect(entry: ConnectableEntry): Promise<void>;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Structurally identical to the app's own `DnaLens` (src/lib/dna-lens.ts) —
|
|
633
|
+
* this package must never import app code, so it declares its own copy
|
|
634
|
+
* rather than sharing the type.
|
|
635
|
+
*/
|
|
636
|
+
type ConnectorPickerLens = "visitor" | "member" | "owner";
|
|
637
|
+
interface ConnectorPickerProps {
|
|
638
|
+
/** The injected transport adapter — required. */
|
|
639
|
+
client: ConnectorPickerClient;
|
|
640
|
+
/** Optional purpose slice, forwarded to `client.listConnectable`. */
|
|
641
|
+
purpose?: string;
|
|
642
|
+
/**
|
|
643
|
+
* Default "owner". "visitor"/"member" render nothing and make ZERO
|
|
644
|
+
* client calls — some host surfaces (e.g. a structurally owner-only
|
|
645
|
+
* knowledge page) have no visitor/member concept at all, and forcing them
|
|
646
|
+
* to synthesize a lens would import dead branches.
|
|
647
|
+
*/
|
|
648
|
+
lens?: ConnectorPickerLens;
|
|
649
|
+
layout?: "tiles" | "list";
|
|
650
|
+
onConnected?: (entry: ConnectableEntry) => void;
|
|
651
|
+
}
|
|
652
|
+
declare function ConnectorPicker({ client, purpose, lens, layout, onConnected, }: ConnectorPickerProps): react.JSX.Element | null;
|
|
653
|
+
|
|
654
|
+
export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ConnectableCapability, type ConnectableEntry, type ConnectableTransport, ConnectorPicker, type ConnectorPickerClient, type ConnectorPickerLens, type ConnectorPickerProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, MARK_WEIGHT, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, WORDMARK_WEIGHT, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var src_exports = {};
|
|
|
33
33
|
__export(src_exports, {
|
|
34
34
|
AppSidebar: () => AppSidebar,
|
|
35
35
|
BrandGlyph: () => BrandGlyph,
|
|
36
|
+
ConnectorPicker: () => ConnectorPicker,
|
|
36
37
|
ContextSwitcher: () => ContextSwitcher,
|
|
37
38
|
Flyout: () => Flyout,
|
|
38
39
|
GLYPH_CANVAS_FILL: () => GLYPH_CANVAS_FILL,
|
|
@@ -1058,11 +1059,11 @@ function AppSidebar({
|
|
|
1058
1059
|
label: item.label,
|
|
1059
1060
|
icon: item.icon,
|
|
1060
1061
|
active,
|
|
1061
|
-
children,
|
|
1062
1062
|
linkComponent,
|
|
1063
1063
|
collapsed: collapsedRow,
|
|
1064
1064
|
forceClosed: openFlyoutId !== null && openFlyoutId !== item.id,
|
|
1065
|
-
onOpenChange: (isOpen) => handleFlyoutOpenChange(item.id, isOpen)
|
|
1065
|
+
onOpenChange: (isOpen) => handleFlyoutOpenChange(item.id, isOpen),
|
|
1066
|
+
children
|
|
1066
1067
|
},
|
|
1067
1068
|
item.id
|
|
1068
1069
|
);
|
|
@@ -1395,10 +1396,263 @@ function ContextSwitcher({
|
|
|
1395
1396
|
)
|
|
1396
1397
|
] });
|
|
1397
1398
|
}
|
|
1399
|
+
|
|
1400
|
+
// src/connector-picker.tsx
|
|
1401
|
+
var import_react9 = require("react");
|
|
1402
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1403
|
+
var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
|
|
1404
|
+
var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
|
|
1405
|
+
var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
|
|
1406
|
+
var SKELETON_COUNT = 3;
|
|
1407
|
+
function capabilityLabel(capability) {
|
|
1408
|
+
if (capability === "read") return "Read only";
|
|
1409
|
+
if (capability === "write") return "Write only";
|
|
1410
|
+
return "Read & write";
|
|
1411
|
+
}
|
|
1412
|
+
function formatSince(since) {
|
|
1413
|
+
const parsed = new Date(since);
|
|
1414
|
+
if (Number.isNaN(parsed.getTime())) return since;
|
|
1415
|
+
return parsed.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
1416
|
+
}
|
|
1417
|
+
function errorMessageFrom(err, fallback) {
|
|
1418
|
+
return err instanceof Error && err.message ? err.message : fallback;
|
|
1419
|
+
}
|
|
1420
|
+
function ConnectorPicker({
|
|
1421
|
+
client,
|
|
1422
|
+
purpose,
|
|
1423
|
+
lens = "owner",
|
|
1424
|
+
layout = "tiles",
|
|
1425
|
+
onConnected
|
|
1426
|
+
}) {
|
|
1427
|
+
const gated = lens !== "owner";
|
|
1428
|
+
const [entries, setEntries] = (0, import_react9.useState)(null);
|
|
1429
|
+
const [loadError, setLoadError] = (0, import_react9.useState)(null);
|
|
1430
|
+
const [uiState, setUiState] = (0, import_react9.useState)({});
|
|
1431
|
+
const clientRef = (0, import_react9.useRef)(client);
|
|
1432
|
+
clientRef.current = client;
|
|
1433
|
+
const purposeRef = (0, import_react9.useRef)(purpose);
|
|
1434
|
+
purposeRef.current = purpose;
|
|
1435
|
+
const onConnectedRef = (0, import_react9.useRef)(onConnected);
|
|
1436
|
+
onConnectedRef.current = onConnected;
|
|
1437
|
+
const uiStateRef = (0, import_react9.useRef)(uiState);
|
|
1438
|
+
uiStateRef.current = uiState;
|
|
1439
|
+
const load = (0, import_react9.useCallback)(async () => {
|
|
1440
|
+
if (gated) return null;
|
|
1441
|
+
try {
|
|
1442
|
+
const list = await clientRef.current.listConnectable({ purpose: purposeRef.current });
|
|
1443
|
+
setEntries(list);
|
|
1444
|
+
setLoadError(null);
|
|
1445
|
+
return list;
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
setLoadError(errorMessageFrom(err, DEFAULT_LOAD_ERROR));
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
}, [gated]);
|
|
1451
|
+
(0, import_react9.useEffect)(() => {
|
|
1452
|
+
if (gated) return;
|
|
1453
|
+
void load();
|
|
1454
|
+
}, [gated, load, purpose]);
|
|
1455
|
+
const refreshAfterReturn = (0, import_react9.useCallback)(async () => {
|
|
1456
|
+
const list = await load();
|
|
1457
|
+
if (!list) return;
|
|
1458
|
+
setUiState((prev) => {
|
|
1459
|
+
let changed = false;
|
|
1460
|
+
const next = { ...prev };
|
|
1461
|
+
for (const entry of list) {
|
|
1462
|
+
if (next[entry.slug]?.kind === "connecting" && entry.connection.status === "connected") {
|
|
1463
|
+
changed = true;
|
|
1464
|
+
delete next[entry.slug];
|
|
1465
|
+
onConnectedRef.current?.(entry);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return changed ? next : prev;
|
|
1469
|
+
});
|
|
1470
|
+
}, [load]);
|
|
1471
|
+
(0, import_react9.useEffect)(() => {
|
|
1472
|
+
if (gated) return;
|
|
1473
|
+
function handleReturn() {
|
|
1474
|
+
if (document.visibilityState === "hidden") return;
|
|
1475
|
+
const hasPendingConnect = Object.values(uiStateRef.current).some(
|
|
1476
|
+
(s) => s.kind === "connecting"
|
|
1477
|
+
);
|
|
1478
|
+
if (!hasPendingConnect) return;
|
|
1479
|
+
void refreshAfterReturn();
|
|
1480
|
+
}
|
|
1481
|
+
window.addEventListener("focus", handleReturn);
|
|
1482
|
+
document.addEventListener("visibilitychange", handleReturn);
|
|
1483
|
+
return () => {
|
|
1484
|
+
window.removeEventListener("focus", handleReturn);
|
|
1485
|
+
document.removeEventListener("visibilitychange", handleReturn);
|
|
1486
|
+
};
|
|
1487
|
+
}, [gated, refreshAfterReturn]);
|
|
1488
|
+
const handleConnect = (0, import_react9.useCallback)(
|
|
1489
|
+
async (entry) => {
|
|
1490
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1491
|
+
try {
|
|
1492
|
+
const result = await clientRef.current.connect(entry);
|
|
1493
|
+
if (result?.connect_url) {
|
|
1494
|
+
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
const list = await load();
|
|
1498
|
+
const updated = list?.find((e) => e.slug === entry.slug) ?? null;
|
|
1499
|
+
setUiState((prev) => {
|
|
1500
|
+
const next = { ...prev };
|
|
1501
|
+
delete next[entry.slug];
|
|
1502
|
+
return next;
|
|
1503
|
+
});
|
|
1504
|
+
if (updated?.connection.status === "connected") {
|
|
1505
|
+
onConnectedRef.current?.(updated);
|
|
1506
|
+
}
|
|
1507
|
+
} catch (err) {
|
|
1508
|
+
const message = errorMessageFrom(err, DEFAULT_CONNECT_ERROR);
|
|
1509
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "error", message } }));
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
[load]
|
|
1513
|
+
);
|
|
1514
|
+
const handleDisconnect = (0, import_react9.useCallback)(
|
|
1515
|
+
async (entry) => {
|
|
1516
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
|
|
1517
|
+
try {
|
|
1518
|
+
await clientRef.current.disconnect(entry);
|
|
1519
|
+
await load();
|
|
1520
|
+
setUiState((prev) => {
|
|
1521
|
+
const next = { ...prev };
|
|
1522
|
+
delete next[entry.slug];
|
|
1523
|
+
return next;
|
|
1524
|
+
});
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
const message = errorMessageFrom(err, DEFAULT_DISCONNECT_ERROR);
|
|
1527
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "error", message } }));
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
[load]
|
|
1531
|
+
);
|
|
1532
|
+
if (gated) return null;
|
|
1533
|
+
const containerClassName = cn(
|
|
1534
|
+
"flex flex-col",
|
|
1535
|
+
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1536
|
+
);
|
|
1537
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-testid": "connector-picker", "data-layout": layout, className: containerClassName, children: [
|
|
1538
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1539
|
+
"div",
|
|
1540
|
+
{
|
|
1541
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1542
|
+
"aria-hidden": "true",
|
|
1543
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1544
|
+
children: [
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1546
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1547
|
+
]
|
|
1548
|
+
},
|
|
1549
|
+
i
|
|
1550
|
+
)),
|
|
1551
|
+
loadError && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1552
|
+
"div",
|
|
1553
|
+
{
|
|
1554
|
+
role: "alert",
|
|
1555
|
+
"data-testid": "connector-picker-load-error",
|
|
1556
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1557
|
+
children: [
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: loadError }),
|
|
1559
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1560
|
+
]
|
|
1561
|
+
}
|
|
1562
|
+
),
|
|
1563
|
+
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." }),
|
|
1564
|
+
entries?.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1565
|
+
ConnectorTile,
|
|
1566
|
+
{
|
|
1567
|
+
entry,
|
|
1568
|
+
ui: uiState[entry.slug],
|
|
1569
|
+
layout,
|
|
1570
|
+
onConnect: handleConnect,
|
|
1571
|
+
onDisconnect: handleDisconnect
|
|
1572
|
+
},
|
|
1573
|
+
entry.slug
|
|
1574
|
+
))
|
|
1575
|
+
] });
|
|
1576
|
+
}
|
|
1577
|
+
function ConnectorTile({
|
|
1578
|
+
entry,
|
|
1579
|
+
ui,
|
|
1580
|
+
layout,
|
|
1581
|
+
onConnect,
|
|
1582
|
+
onDisconnect
|
|
1583
|
+
}) {
|
|
1584
|
+
const isConnected = entry.connection.status === "connected";
|
|
1585
|
+
const isConnecting = ui?.kind === "connecting";
|
|
1586
|
+
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1587
|
+
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1588
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1589
|
+
"div",
|
|
1590
|
+
{
|
|
1591
|
+
"data-testid": `connector-tile-${entry.slug}`,
|
|
1592
|
+
"data-connected": isConnected || void 0,
|
|
1593
|
+
"data-deprecated": entry.deprecated || void 0,
|
|
1594
|
+
className: cn("flex items-center gap-3 p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1595
|
+
children: [
|
|
1596
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1597
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
1599
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1600
|
+
entry.deprecated && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1601
|
+
"span",
|
|
1602
|
+
{
|
|
1603
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1604
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1605
|
+
children: "No longer available"
|
|
1606
|
+
}
|
|
1607
|
+
)
|
|
1608
|
+
] }),
|
|
1609
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1610
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1611
|
+
entry.category && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1613
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate", children: entry.category })
|
|
1614
|
+
] })
|
|
1615
|
+
] }),
|
|
1616
|
+
isConnected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1617
|
+
"p",
|
|
1618
|
+
{
|
|
1619
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1620
|
+
className: "text-xs text-muted-foreground",
|
|
1621
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1622
|
+
}
|
|
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 })
|
|
1625
|
+
] }),
|
|
1626
|
+
isConnected ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1627
|
+
"button",
|
|
1628
|
+
{
|
|
1629
|
+
type: "button",
|
|
1630
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1631
|
+
onClick: () => onDisconnect(entry),
|
|
1632
|
+
disabled: isDisconnecting,
|
|
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"
|
|
1635
|
+
}
|
|
1636
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1637
|
+
"button",
|
|
1638
|
+
{
|
|
1639
|
+
type: "button",
|
|
1640
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1641
|
+
onClick: () => onConnect(entry),
|
|
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"
|
|
1645
|
+
}
|
|
1646
|
+
)
|
|
1647
|
+
]
|
|
1648
|
+
}
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1398
1651
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1399
1652
|
0 && (module.exports = {
|
|
1400
1653
|
AppSidebar,
|
|
1401
1654
|
BrandGlyph,
|
|
1655
|
+
ConnectorPicker,
|
|
1402
1656
|
ContextSwitcher,
|
|
1403
1657
|
Flyout,
|
|
1404
1658
|
GLYPH_CANVAS_FILL,
|
package/dist/index.mjs
CHANGED
|
@@ -1004,11 +1004,11 @@ function AppSidebar({
|
|
|
1004
1004
|
label: item.label,
|
|
1005
1005
|
icon: item.icon,
|
|
1006
1006
|
active,
|
|
1007
|
-
children,
|
|
1008
1007
|
linkComponent,
|
|
1009
1008
|
collapsed: collapsedRow,
|
|
1010
1009
|
forceClosed: openFlyoutId !== null && openFlyoutId !== item.id,
|
|
1011
|
-
onOpenChange: (isOpen) => handleFlyoutOpenChange(item.id, isOpen)
|
|
1010
|
+
onOpenChange: (isOpen) => handleFlyoutOpenChange(item.id, isOpen),
|
|
1011
|
+
children
|
|
1012
1012
|
},
|
|
1013
1013
|
item.id
|
|
1014
1014
|
);
|
|
@@ -1341,9 +1341,262 @@ function ContextSwitcher({
|
|
|
1341
1341
|
)
|
|
1342
1342
|
] });
|
|
1343
1343
|
}
|
|
1344
|
+
|
|
1345
|
+
// src/connector-picker.tsx
|
|
1346
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef5, useState as useState7 } from "react";
|
|
1347
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1348
|
+
var DEFAULT_CONNECT_ERROR = "Couldn't start the connection. Please try again.";
|
|
1349
|
+
var DEFAULT_DISCONNECT_ERROR = "Couldn't disconnect. Please try again.";
|
|
1350
|
+
var DEFAULT_LOAD_ERROR = "Couldn't load connections. Please try again.";
|
|
1351
|
+
var SKELETON_COUNT = 3;
|
|
1352
|
+
function capabilityLabel(capability) {
|
|
1353
|
+
if (capability === "read") return "Read only";
|
|
1354
|
+
if (capability === "write") return "Write only";
|
|
1355
|
+
return "Read & write";
|
|
1356
|
+
}
|
|
1357
|
+
function formatSince(since) {
|
|
1358
|
+
const parsed = new Date(since);
|
|
1359
|
+
if (Number.isNaN(parsed.getTime())) return since;
|
|
1360
|
+
return parsed.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
|
|
1361
|
+
}
|
|
1362
|
+
function errorMessageFrom(err, fallback) {
|
|
1363
|
+
return err instanceof Error && err.message ? err.message : fallback;
|
|
1364
|
+
}
|
|
1365
|
+
function ConnectorPicker({
|
|
1366
|
+
client,
|
|
1367
|
+
purpose,
|
|
1368
|
+
lens = "owner",
|
|
1369
|
+
layout = "tiles",
|
|
1370
|
+
onConnected
|
|
1371
|
+
}) {
|
|
1372
|
+
const gated = lens !== "owner";
|
|
1373
|
+
const [entries, setEntries] = useState7(null);
|
|
1374
|
+
const [loadError, setLoadError] = useState7(null);
|
|
1375
|
+
const [uiState, setUiState] = useState7({});
|
|
1376
|
+
const clientRef = useRef5(client);
|
|
1377
|
+
clientRef.current = client;
|
|
1378
|
+
const purposeRef = useRef5(purpose);
|
|
1379
|
+
purposeRef.current = purpose;
|
|
1380
|
+
const onConnectedRef = useRef5(onConnected);
|
|
1381
|
+
onConnectedRef.current = onConnected;
|
|
1382
|
+
const uiStateRef = useRef5(uiState);
|
|
1383
|
+
uiStateRef.current = uiState;
|
|
1384
|
+
const load = useCallback4(async () => {
|
|
1385
|
+
if (gated) return null;
|
|
1386
|
+
try {
|
|
1387
|
+
const list = await clientRef.current.listConnectable({ purpose: purposeRef.current });
|
|
1388
|
+
setEntries(list);
|
|
1389
|
+
setLoadError(null);
|
|
1390
|
+
return list;
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
setLoadError(errorMessageFrom(err, DEFAULT_LOAD_ERROR));
|
|
1393
|
+
return null;
|
|
1394
|
+
}
|
|
1395
|
+
}, [gated]);
|
|
1396
|
+
useEffect6(() => {
|
|
1397
|
+
if (gated) return;
|
|
1398
|
+
void load();
|
|
1399
|
+
}, [gated, load, purpose]);
|
|
1400
|
+
const refreshAfterReturn = useCallback4(async () => {
|
|
1401
|
+
const list = await load();
|
|
1402
|
+
if (!list) return;
|
|
1403
|
+
setUiState((prev) => {
|
|
1404
|
+
let changed = false;
|
|
1405
|
+
const next = { ...prev };
|
|
1406
|
+
for (const entry of list) {
|
|
1407
|
+
if (next[entry.slug]?.kind === "connecting" && entry.connection.status === "connected") {
|
|
1408
|
+
changed = true;
|
|
1409
|
+
delete next[entry.slug];
|
|
1410
|
+
onConnectedRef.current?.(entry);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return changed ? next : prev;
|
|
1414
|
+
});
|
|
1415
|
+
}, [load]);
|
|
1416
|
+
useEffect6(() => {
|
|
1417
|
+
if (gated) return;
|
|
1418
|
+
function handleReturn() {
|
|
1419
|
+
if (document.visibilityState === "hidden") return;
|
|
1420
|
+
const hasPendingConnect = Object.values(uiStateRef.current).some(
|
|
1421
|
+
(s) => s.kind === "connecting"
|
|
1422
|
+
);
|
|
1423
|
+
if (!hasPendingConnect) return;
|
|
1424
|
+
void refreshAfterReturn();
|
|
1425
|
+
}
|
|
1426
|
+
window.addEventListener("focus", handleReturn);
|
|
1427
|
+
document.addEventListener("visibilitychange", handleReturn);
|
|
1428
|
+
return () => {
|
|
1429
|
+
window.removeEventListener("focus", handleReturn);
|
|
1430
|
+
document.removeEventListener("visibilitychange", handleReturn);
|
|
1431
|
+
};
|
|
1432
|
+
}, [gated, refreshAfterReturn]);
|
|
1433
|
+
const handleConnect = useCallback4(
|
|
1434
|
+
async (entry) => {
|
|
1435
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "connecting" } }));
|
|
1436
|
+
try {
|
|
1437
|
+
const result = await clientRef.current.connect(entry);
|
|
1438
|
+
if (result?.connect_url) {
|
|
1439
|
+
window.open(result.connect_url, "_blank", "noopener,noreferrer");
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const list = await load();
|
|
1443
|
+
const updated = list?.find((e) => e.slug === entry.slug) ?? null;
|
|
1444
|
+
setUiState((prev) => {
|
|
1445
|
+
const next = { ...prev };
|
|
1446
|
+
delete next[entry.slug];
|
|
1447
|
+
return next;
|
|
1448
|
+
});
|
|
1449
|
+
if (updated?.connection.status === "connected") {
|
|
1450
|
+
onConnectedRef.current?.(updated);
|
|
1451
|
+
}
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
const message = errorMessageFrom(err, DEFAULT_CONNECT_ERROR);
|
|
1454
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "error", message } }));
|
|
1455
|
+
}
|
|
1456
|
+
},
|
|
1457
|
+
[load]
|
|
1458
|
+
);
|
|
1459
|
+
const handleDisconnect = useCallback4(
|
|
1460
|
+
async (entry) => {
|
|
1461
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "disconnecting" } }));
|
|
1462
|
+
try {
|
|
1463
|
+
await clientRef.current.disconnect(entry);
|
|
1464
|
+
await load();
|
|
1465
|
+
setUiState((prev) => {
|
|
1466
|
+
const next = { ...prev };
|
|
1467
|
+
delete next[entry.slug];
|
|
1468
|
+
return next;
|
|
1469
|
+
});
|
|
1470
|
+
} catch (err) {
|
|
1471
|
+
const message = errorMessageFrom(err, DEFAULT_DISCONNECT_ERROR);
|
|
1472
|
+
setUiState((prev) => ({ ...prev, [entry.slug]: { kind: "error", message } }));
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
[load]
|
|
1476
|
+
);
|
|
1477
|
+
if (gated) return null;
|
|
1478
|
+
const containerClassName = cn(
|
|
1479
|
+
"flex flex-col",
|
|
1480
|
+
layout === "tiles" ? "gap-3 sm:grid sm:grid-cols-2 lg:grid-cols-3" : "divide-y divide-border"
|
|
1481
|
+
);
|
|
1482
|
+
return /* @__PURE__ */ jsxs6("div", { "data-testid": "connector-picker", "data-layout": layout, className: containerClassName, children: [
|
|
1483
|
+
entries === null && !loadError && Array.from({ length: SKELETON_COUNT }).map((_, i) => /* @__PURE__ */ jsxs6(
|
|
1484
|
+
"div",
|
|
1485
|
+
{
|
|
1486
|
+
"data-testid": "connector-picker-skeleton-item",
|
|
1487
|
+
"aria-hidden": "true",
|
|
1488
|
+
className: "flex items-center gap-3 rounded-lg border border-border p-3",
|
|
1489
|
+
children: [
|
|
1490
|
+
/* @__PURE__ */ jsx7("span", { className: "h-8 w-8 shrink-0 animate-pulse rounded bg-foreground/10" }),
|
|
1491
|
+
/* @__PURE__ */ jsx7("span", { className: "h-3 flex-1 animate-pulse rounded bg-foreground/10" })
|
|
1492
|
+
]
|
|
1493
|
+
},
|
|
1494
|
+
i
|
|
1495
|
+
)),
|
|
1496
|
+
loadError && /* @__PURE__ */ jsxs6(
|
|
1497
|
+
"div",
|
|
1498
|
+
{
|
|
1499
|
+
role: "alert",
|
|
1500
|
+
"data-testid": "connector-picker-load-error",
|
|
1501
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-destructive/30 bg-destructive/5 p-3 text-sm text-destructive",
|
|
1502
|
+
children: [
|
|
1503
|
+
/* @__PURE__ */ jsx7("span", { children: loadError }),
|
|
1504
|
+
/* @__PURE__ */ jsx7("button", { type: "button", onClick: () => void load(), className: "shrink-0 underline", children: "Retry" })
|
|
1505
|
+
]
|
|
1506
|
+
}
|
|
1507
|
+
),
|
|
1508
|
+
entries !== null && entries.length === 0 && !loadError && /* @__PURE__ */ jsx7("p", { "data-testid": "connector-picker-empty", className: "text-sm text-muted-foreground", children: "No connections available." }),
|
|
1509
|
+
entries?.map((entry) => /* @__PURE__ */ jsx7(
|
|
1510
|
+
ConnectorTile,
|
|
1511
|
+
{
|
|
1512
|
+
entry,
|
|
1513
|
+
ui: uiState[entry.slug],
|
|
1514
|
+
layout,
|
|
1515
|
+
onConnect: handleConnect,
|
|
1516
|
+
onDisconnect: handleDisconnect
|
|
1517
|
+
},
|
|
1518
|
+
entry.slug
|
|
1519
|
+
))
|
|
1520
|
+
] });
|
|
1521
|
+
}
|
|
1522
|
+
function ConnectorTile({
|
|
1523
|
+
entry,
|
|
1524
|
+
ui,
|
|
1525
|
+
layout,
|
|
1526
|
+
onConnect,
|
|
1527
|
+
onDisconnect
|
|
1528
|
+
}) {
|
|
1529
|
+
const isConnected = entry.connection.status === "connected";
|
|
1530
|
+
const isConnecting = ui?.kind === "connecting";
|
|
1531
|
+
const isDisconnecting = ui?.kind === "disconnecting";
|
|
1532
|
+
const errorMessage = ui?.kind === "error" ? ui.message : null;
|
|
1533
|
+
return /* @__PURE__ */ jsxs6(
|
|
1534
|
+
"div",
|
|
1535
|
+
{
|
|
1536
|
+
"data-testid": `connector-tile-${entry.slug}`,
|
|
1537
|
+
"data-connected": isConnected || void 0,
|
|
1538
|
+
"data-deprecated": entry.deprecated || void 0,
|
|
1539
|
+
className: cn("flex items-center gap-3 p-3", layout === "tiles" && "rounded-lg border border-border"),
|
|
1540
|
+
children: [
|
|
1541
|
+
/* @__PURE__ */ jsx7(BrandGlyph, { brand: { name: entry.name, logo: entry.logo }, size: "lg" }),
|
|
1542
|
+
/* @__PURE__ */ jsxs6("div", { className: "min-w-0 flex-1", children: [
|
|
1543
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
1544
|
+
/* @__PURE__ */ jsx7("p", { className: "truncate text-sm font-medium", children: entry.name }),
|
|
1545
|
+
entry.deprecated && /* @__PURE__ */ jsx7(
|
|
1546
|
+
"span",
|
|
1547
|
+
{
|
|
1548
|
+
"data-testid": `connector-deprecated-${entry.slug}`,
|
|
1549
|
+
className: "shrink-0 rounded-full bg-foreground/10 px-2 py-0.5 text-[11px] text-muted-foreground",
|
|
1550
|
+
children: "No longer available"
|
|
1551
|
+
}
|
|
1552
|
+
)
|
|
1553
|
+
] }),
|
|
1554
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1555
|
+
/* @__PURE__ */ jsx7("span", { "data-testid": `connector-capability-${entry.slug}`, children: capabilityLabel(entry.capability) }),
|
|
1556
|
+
entry.category && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
1557
|
+
/* @__PURE__ */ jsx7("span", { "aria-hidden": "true", children: "\xB7" }),
|
|
1558
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate", children: entry.category })
|
|
1559
|
+
] })
|
|
1560
|
+
] }),
|
|
1561
|
+
isConnected && /* @__PURE__ */ jsx7(
|
|
1562
|
+
"p",
|
|
1563
|
+
{
|
|
1564
|
+
"data-testid": `connector-connected-since-${entry.slug}`,
|
|
1565
|
+
className: "text-xs text-muted-foreground",
|
|
1566
|
+
children: entry.connection.since ? `Connected since ${formatSince(entry.connection.since)}` : "Connected"
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
errorMessage && /* @__PURE__ */ jsx7("p", { role: "alert", "data-testid": `connector-error-${entry.slug}`, className: "text-xs text-destructive", children: errorMessage })
|
|
1570
|
+
] }),
|
|
1571
|
+
isConnected ? /* @__PURE__ */ jsx7(
|
|
1572
|
+
"button",
|
|
1573
|
+
{
|
|
1574
|
+
type: "button",
|
|
1575
|
+
"data-testid": `connector-disconnect-${entry.slug}`,
|
|
1576
|
+
onClick: () => onDisconnect(entry),
|
|
1577
|
+
disabled: isDisconnecting,
|
|
1578
|
+
className: "shrink-0 rounded-md px-2.5 py-1 text-xs font-medium text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
1579
|
+
children: isDisconnecting ? "Disconnecting\u2026" : "Disconnect"
|
|
1580
|
+
}
|
|
1581
|
+
) : /* @__PURE__ */ jsx7(
|
|
1582
|
+
"button",
|
|
1583
|
+
{
|
|
1584
|
+
type: "button",
|
|
1585
|
+
"data-testid": `connector-connect-${entry.slug}`,
|
|
1586
|
+
onClick: () => onConnect(entry),
|
|
1587
|
+
disabled: isConnecting,
|
|
1588
|
+
className: "shrink-0 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50",
|
|
1589
|
+
children: isConnecting ? "Connecting\u2026" : "Connect"
|
|
1590
|
+
}
|
|
1591
|
+
)
|
|
1592
|
+
]
|
|
1593
|
+
}
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1344
1596
|
export {
|
|
1345
1597
|
AppSidebar,
|
|
1346
1598
|
BrandGlyph,
|
|
1599
|
+
ConnectorPicker,
|
|
1347
1600
|
ContextSwitcher,
|
|
1348
1601
|
Flyout,
|
|
1349
1602
|
GLYPH_CANVAS_FILL,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meetreeve/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dev": "tsup --watch",
|
|
36
36
|
"test": "vitest run",
|
|
37
37
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"check:esm": "node scripts/check-esm-import.mjs",
|
|
38
|
+
"check:esm": "node ../../scripts/check-esm-import.mjs .",
|
|
39
39
|
"prepublishOnly": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@testing-library/dom": "^10.4.1",
|
|
56
56
|
"@testing-library/react": "^16.3.2",
|
|
57
57
|
"@testing-library/user-event": "^14.6.1",
|
|
58
|
+
"@types/node": "^20.19.43",
|
|
58
59
|
"@types/react": "^19",
|
|
59
60
|
"@types/react-dom": "^19",
|
|
60
61
|
"@vitejs/plugin-react": "^5.1.3",
|