arc402-cli 0.9.18 → 0.10.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/README.md +41 -2
- package/dist/abis.d.ts +1 -0
- package/dist/abis.d.ts.map +1 -1
- package/dist/abis.js +45 -14
- package/dist/abis.js.map +1 -1
- package/dist/bundler.d.ts +1 -1
- package/dist/bundler.d.ts.map +1 -1
- package/dist/bundler.js +61 -27
- package/dist/bundler.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +9 -5
- package/dist/client.js.map +1 -1
- package/dist/coinbase-smart-wallet.js +4 -1
- package/dist/coinbase-smart-wallet.js.map +1 -1
- package/dist/commands/accept.js +28 -25
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/agent-handshake.js +18 -15
- package/dist/commands/agent-handshake.js.map +1 -1
- package/dist/commands/agent.js +104 -98
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/agreements.js +98 -62
- package/dist/commands/agreements.js.map +1 -1
- package/dist/commands/arbitrator.js +81 -45
- package/dist/commands/arbitrator.js.map +1 -1
- package/dist/commands/arena-handshake.d.ts.map +1 -1
- package/dist/commands/arena-handshake.js +35 -53
- package/dist/commands/arena-handshake.js.map +1 -1
- package/dist/commands/arena.js +18 -12
- package/dist/commands/arena.js.map +1 -1
- package/dist/commands/backup.js +36 -30
- package/dist/commands/backup.js.map +1 -1
- package/dist/commands/cancel.js +18 -15
- package/dist/commands/cancel.js.map +1 -1
- package/dist/commands/channel.js +81 -45
- package/dist/commands/channel.js.map +1 -1
- package/dist/commands/coldstart.js +34 -31
- package/dist/commands/coldstart.js.map +1 -1
- package/dist/commands/compute.d.ts +14 -0
- package/dist/commands/compute.d.ts.map +1 -0
- package/dist/commands/compute.js +466 -0
- package/dist/commands/compute.js.map +1 -0
- package/dist/commands/config.js +30 -24
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/contract-interaction.js +15 -12
- package/dist/commands/contract-interaction.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +135 -98
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deliver.js +76 -37
- package/dist/commands/deliver.js.map +1 -1
- package/dist/commands/discover.js +27 -24
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dispute.js +110 -104
- package/dist/commands/dispute.js.map +1 -1
- package/dist/commands/doctor.js +55 -16
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/endpoint.js +95 -56
- package/dist/commands/endpoint.js.map +1 -1
- package/dist/commands/feed.js +18 -11
- package/dist/commands/feed.js.map +1 -1
- package/dist/commands/hire.js +40 -37
- package/dist/commands/hire.js.map +1 -1
- package/dist/commands/migrate.js +33 -30
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/negotiate.d.ts.map +1 -1
- package/dist/commands/negotiate.js +36 -34
- package/dist/commands/negotiate.js.map +1 -1
- package/dist/commands/openshell.js +104 -68
- package/dist/commands/openshell.js.map +1 -1
- package/dist/commands/owner.js +20 -17
- package/dist/commands/owner.js.map +1 -1
- package/dist/commands/policy.js +43 -41
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/relay.d.ts.map +1 -1
- package/dist/commands/relay.js +51 -18
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/remediate.js +23 -20
- package/dist/commands/remediate.js.map +1 -1
- package/dist/commands/reputation.js +27 -25
- package/dist/commands/reputation.js.map +1 -1
- package/dist/commands/setup.js +104 -65
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/trust.js +20 -17
- package/dist/commands/trust.js.map +1 -1
- package/dist/commands/verify.js +21 -18
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +645 -679
- package/dist/commands/wallet.js.map +1 -1
- package/dist/commands/watch.js +36 -33
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/watchtower.js +73 -37
- package/dist/commands/watchtower.js.map +1 -1
- package/dist/commands/workroom.d.ts.map +1 -1
- package/dist/commands/workroom.js +282 -143
- package/dist/commands/workroom.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +71 -22
- package/dist/config.js.map +1 -1
- package/dist/daemon/compute-metering.d.ts +61 -0
- package/dist/daemon/compute-metering.d.ts.map +1 -0
- package/dist/daemon/compute-metering.js +299 -0
- package/dist/daemon/compute-metering.js.map +1 -0
- package/dist/daemon/compute-session.d.ts +100 -0
- package/dist/daemon/compute-session.d.ts.map +1 -0
- package/dist/daemon/compute-session.js +231 -0
- package/dist/daemon/compute-session.js.map +1 -0
- package/dist/daemon/config.d.ts +19 -1
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +90 -16
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/credentials.d.ts +24 -0
- package/dist/daemon/credentials.d.ts.map +1 -0
- package/dist/daemon/credentials.js +80 -0
- package/dist/daemon/credentials.js.map +1 -0
- package/dist/daemon/delivery-client.d.ts +35 -0
- package/dist/daemon/delivery-client.d.ts.map +1 -0
- package/dist/daemon/delivery-client.js +231 -0
- package/dist/daemon/delivery-client.js.map +1 -0
- package/dist/daemon/file-delivery.d.ts +98 -0
- package/dist/daemon/file-delivery.d.ts.map +1 -0
- package/dist/daemon/file-delivery.js +461 -0
- package/dist/daemon/file-delivery.js.map +1 -0
- package/dist/daemon/hire-listener.d.ts +3 -3
- package/dist/daemon/hire-listener.d.ts.map +1 -1
- package/dist/daemon/hire-listener.js +47 -13
- package/dist/daemon/hire-listener.js.map +1 -1
- package/dist/daemon/index.d.ts +2 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +526 -53
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/job-lifecycle.d.ts +1 -1
- package/dist/daemon/job-lifecycle.d.ts.map +1 -1
- package/dist/daemon/job-lifecycle.js +51 -11
- package/dist/daemon/job-lifecycle.js.map +1 -1
- package/dist/daemon/notify.d.ts +1 -1
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +53 -19
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/token-metering.js +47 -8
- package/dist/daemon/token-metering.js.map +1 -1
- package/dist/daemon/userops.d.ts +2 -2
- package/dist/daemon/userops.d.ts.map +1 -1
- package/dist/daemon/userops.js +27 -23
- package/dist/daemon/userops.js.map +1 -1
- package/dist/daemon/wallet-monitor.d.ts +1 -1
- package/dist/daemon/wallet-monitor.d.ts.map +1 -1
- package/dist/daemon/wallet-monitor.js +12 -8
- package/dist/daemon/wallet-monitor.js.map +1 -1
- package/dist/daemon/worker-executor.d.ts +71 -0
- package/dist/daemon/worker-executor.d.ts.map +1 -0
- package/dist/daemon/worker-executor.js +382 -0
- package/dist/daemon/worker-executor.js.map +1 -0
- package/dist/drain-v4.js +64 -26
- package/dist/drain-v4.js.map +1 -1
- package/dist/endpoint-config.js +63 -20
- package/dist/endpoint-config.js.map +1 -1
- package/dist/endpoint-notify.js +48 -9
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +50 -18
- package/dist/index.js.map +1 -1
- package/dist/openshell-runtime.d.ts.map +1 -1
- package/dist/openshell-runtime.js +82 -38
- package/dist/openshell-runtime.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +85 -78
- package/dist/program.js.map +1 -1
- package/dist/repl.js +31 -25
- package/dist/repl.js.map +1 -1
- package/dist/signing.js +6 -3
- package/dist/signing.js.map +1 -1
- package/dist/telegram-notify.js +40 -3
- package/dist/telegram-notify.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +56 -89
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/Footer.js +7 -4
- package/dist/tui/Footer.js.map +1 -1
- package/dist/tui/Header.d.ts +1 -1
- package/dist/tui/Header.d.ts.map +1 -1
- package/dist/tui/Header.js +14 -9
- package/dist/tui/Header.js.map +1 -1
- package/dist/tui/InputLine.d.ts +2 -1
- package/dist/tui/InputLine.d.ts.map +1 -1
- package/dist/tui/InputLine.js +47 -97
- package/dist/tui/InputLine.js.map +1 -1
- package/dist/tui/Viewport.d.ts +1 -2
- package/dist/tui/Viewport.d.ts.map +1 -1
- package/dist/tui/Viewport.js +26 -6
- package/dist/tui/Viewport.js.map +1 -1
- package/dist/tui/WalletConnectPairing.js +19 -16
- package/dist/tui/WalletConnectPairing.js.map +1 -1
- package/dist/tui/components/Button.js +9 -6
- package/dist/tui/components/Button.js.map +1 -1
- package/dist/tui/components/CeremonyView.js +8 -5
- package/dist/tui/components/CeremonyView.js.map +1 -1
- package/dist/tui/components/CompletionDropdown.js +9 -6
- package/dist/tui/components/CompletionDropdown.js.map +1 -1
- package/dist/tui/components/ConfirmPrompt.js +8 -5
- package/dist/tui/components/ConfirmPrompt.js.map +1 -1
- package/dist/tui/components/CustomTextInput.js +14 -11
- package/dist/tui/components/CustomTextInput.js.map +1 -1
- package/dist/tui/components/InteractiveTable.js +12 -9
- package/dist/tui/components/InteractiveTable.js.map +1 -1
- package/dist/tui/components/StepSpinner.js +13 -10
- package/dist/tui/components/StepSpinner.js.map +1 -1
- package/dist/tui/components/Toast.js +12 -8
- package/dist/tui/components/Toast.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +21 -28
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/useChat.js +19 -13
- package/dist/tui/useChat.js.map +1 -1
- package/dist/tui/useCommand.d.ts +2 -3
- package/dist/tui/useCommand.d.ts.map +1 -1
- package/dist/tui/useCommand.js +24 -100
- package/dist/tui/useCommand.js.map +1 -1
- package/dist/tui/useNotifications.js +8 -5
- package/dist/tui/useNotifications.js.map +1 -1
- package/dist/tui/useScroll.d.ts.map +1 -1
- package/dist/tui/useScroll.js +12 -15
- package/dist/tui/useScroll.js.map +1 -1
- package/dist/ui/banner.d.ts +0 -12
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +19 -35
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/colors.js +19 -13
- package/dist/ui/colors.js.map +1 -1
- package/dist/ui/format.js +14 -6
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/qr-render.js +11 -4
- package/dist/ui/qr-render.js.map +1 -1
- package/dist/ui/rpc-fallback.js +11 -6
- package/dist/ui/rpc-fallback.js.map +1 -1
- package/dist/ui/spinner.js +12 -6
- package/dist/ui/spinner.js.map +1 -1
- package/dist/ui/tree.js +6 -3
- package/dist/ui/tree.js.map +1 -1
- package/dist/utils/format.js +41 -27
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/hash.js +42 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/time.js +6 -2
- package/dist/utils/time.js.map +1 -1
- package/dist/wallet-router.d.ts +1 -1
- package/dist/wallet-router.d.ts.map +1 -1
- package/dist/wallet-router.js +19 -12
- package/dist/wallet-router.js.map +1 -1
- package/dist/walletconnect-session.d.ts +1 -1
- package/dist/walletconnect-session.d.ts.map +1 -1
- package/dist/walletconnect-session.js +11 -6
- package/dist/walletconnect-session.js.map +1 -1
- package/dist/walletconnect.d.ts +5 -6
- package/dist/walletconnect.d.ts.map +1 -1
- package/dist/walletconnect.js +35 -32
- package/dist/walletconnect.js.map +1 -1
- package/package.json +11 -10
- package/INK6-UX-SPEC.md +0 -446
- package/MIGRATION-SPEC.md +0 -108
- package/TUI-SPEC.md +0 -214
- package/scripts/authorize-machine-key.ts +0 -43
- package/scripts/drain-wallet.ts +0 -149
- package/scripts/execute-spend-only.ts +0 -81
- package/scripts/register-agent-userop.ts +0 -186
- package/src/abis.ts +0 -187
- package/src/bundler.ts +0 -235
- package/src/client.ts +0 -36
- package/src/coinbase-smart-wallet.ts +0 -51
- package/src/commands/accept.ts +0 -64
- package/src/commands/agent-handshake.ts +0 -72
- package/src/commands/agent.ts +0 -691
- package/src/commands/agreements.ts +0 -350
- package/src/commands/arbitrator.ts +0 -180
- package/src/commands/arena-handshake.ts +0 -274
- package/src/commands/arena.ts +0 -122
- package/src/commands/backup.ts +0 -117
- package/src/commands/cancel.ts +0 -35
- package/src/commands/channel.ts +0 -218
- package/src/commands/coldstart.ts +0 -165
- package/src/commands/config.ts +0 -68
- package/src/commands/contract-interaction.ts +0 -166
- package/src/commands/daemon.ts +0 -1054
- package/src/commands/deliver.ts +0 -148
- package/src/commands/discover.ts +0 -350
- package/src/commands/dispute.ts +0 -375
- package/src/commands/doctor.ts +0 -172
- package/src/commands/endpoint.ts +0 -620
- package/src/commands/feed.ts +0 -229
- package/src/commands/hire.ts +0 -245
- package/src/commands/migrate.ts +0 -177
- package/src/commands/negotiate.ts +0 -272
- package/src/commands/openshell.ts +0 -1055
- package/src/commands/owner.ts +0 -35
- package/src/commands/policy.ts +0 -263
- package/src/commands/relay.ts +0 -277
- package/src/commands/remediate.ts +0 -24
- package/src/commands/reputation.ts +0 -79
- package/src/commands/setup.ts +0 -343
- package/src/commands/trust.ts +0 -27
- package/src/commands/verify.ts +0 -91
- package/src/commands/wallet.ts +0 -3548
- package/src/commands/watch.ts +0 -220
- package/src/commands/watchtower.ts +0 -248
- package/src/commands/workroom.ts +0 -963
- package/src/config.ts +0 -220
- package/src/daemon/config.ts +0 -344
- package/src/daemon/hire-listener.ts +0 -226
- package/src/daemon/index.ts +0 -1089
- package/src/daemon/job-lifecycle.ts +0 -215
- package/src/daemon/notify.ts +0 -297
- package/src/daemon/token-metering.ts +0 -183
- package/src/daemon/userops.ts +0 -119
- package/src/daemon/wallet-monitor.ts +0 -90
- package/src/drain-v4.ts +0 -159
- package/src/endpoint-config.ts +0 -83
- package/src/endpoint-notify.ts +0 -129
- package/src/index.ts +0 -74
- package/src/openshell-runtime.ts +0 -281
- package/src/program.ts +0 -88
- package/src/repl.ts +0 -178
- package/src/signing.ts +0 -28
- package/src/telegram-notify.ts +0 -88
- package/src/tui/App.tsx +0 -263
- package/src/tui/Footer.tsx +0 -18
- package/src/tui/Header.tsx +0 -45
- package/src/tui/InputLine.tsx +0 -243
- package/src/tui/Viewport.tsx +0 -51
- package/src/tui/WalletConnectPairing.tsx +0 -114
- package/src/tui/components/Button.tsx +0 -38
- package/src/tui/components/CeremonyView.tsx +0 -39
- package/src/tui/components/CompletionDropdown.tsx +0 -56
- package/src/tui/components/ConfirmPrompt.tsx +0 -36
- package/src/tui/components/CustomTextInput.tsx +0 -132
- package/src/tui/components/InteractiveTable.tsx +0 -106
- package/src/tui/components/StepSpinner.tsx +0 -84
- package/src/tui/components/Toast.tsx +0 -59
- package/src/tui/index.tsx +0 -90
- package/src/tui/useChat.ts +0 -103
- package/src/tui/useCommand.ts +0 -238
- package/src/tui/useNotifications.ts +0 -28
- package/src/tui/useScroll.ts +0 -69
- package/src/ui/banner.ts +0 -78
- package/src/ui/colors.ts +0 -30
- package/src/ui/format.ts +0 -78
- package/src/ui/qr-render.ts +0 -92
- package/src/ui/rpc-fallback.ts +0 -59
- package/src/ui/spinner.ts +0 -56
- package/src/ui/tree.ts +0 -16
- package/src/utils/format.ts +0 -48
- package/src/utils/hash.ts +0 -5
- package/src/utils/time.ts +0 -15
- package/src/wallet-router.ts +0 -178
- package/src/walletconnect-session.ts +0 -27
- package/src/walletconnect.ts +0 -309
- package/test/time.test.js +0 -11
- package/tsconfig.json +0 -33
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import type { WCCallbacks } from "../walletconnect.js";
|
|
4
|
-
import { renderQR } from "../ui/qr-render.js";
|
|
5
|
-
|
|
6
|
-
export type WCStage = "connecting" | "connected" | "chain-switching" | "ready" | "error";
|
|
7
|
-
|
|
8
|
-
interface WalletConnectPairingProps {
|
|
9
|
-
projectId: string;
|
|
10
|
-
chainId: number;
|
|
11
|
-
onComplete: (result: { account: string }) => void;
|
|
12
|
-
onError: (err: string) => void;
|
|
13
|
-
/** Called once the component mounts — parent passes the connect function */
|
|
14
|
-
connect: (callbacks: WCCallbacks) => Promise<{ account: string }>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Renders WalletConnect pairing inside the Ink TUI viewport:
|
|
19
|
-
* - ASCII QR code
|
|
20
|
-
* - Deep links for MetaMask, Rainbow, Trust, etc.
|
|
21
|
-
* - Status transitions: connecting → connected → chain-switching → ready
|
|
22
|
-
*/
|
|
23
|
-
export function WalletConnectPairing({
|
|
24
|
-
onComplete,
|
|
25
|
-
onError,
|
|
26
|
-
connect,
|
|
27
|
-
}: WalletConnectPairingProps) {
|
|
28
|
-
const [stage, setStage] = useState<WCStage>("connecting");
|
|
29
|
-
const [uri, setUri] = useState<string | null>(null);
|
|
30
|
-
const [links, setLinks] = useState<Record<string, string>>({});
|
|
31
|
-
const [account, setAccount] = useState<string | null>(null);
|
|
32
|
-
const [qrLines, setQrLines] = useState<string[]>([]);
|
|
33
|
-
const [detail, setDetail] = useState<string>("");
|
|
34
|
-
|
|
35
|
-
const handleUri = useCallback((wcUri: string, wcLinks: Record<string, string>) => {
|
|
36
|
-
setUri(wcUri);
|
|
37
|
-
setLinks(wcLinks);
|
|
38
|
-
// Generate ASCII QR using our compact renderer
|
|
39
|
-
try {
|
|
40
|
-
const lines = renderQR(wcUri);
|
|
41
|
-
setQrLines(lines);
|
|
42
|
-
} catch {
|
|
43
|
-
// QR rendering is best-effort
|
|
44
|
-
}
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
const handleStatus = useCallback((status: WCStage, statusDetail?: string) => {
|
|
48
|
-
setStage(status);
|
|
49
|
-
if (statusDetail) setDetail(statusDetail);
|
|
50
|
-
if (status === "connected" && statusDetail) {
|
|
51
|
-
setAccount(statusDetail);
|
|
52
|
-
}
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
const callbacks: WCCallbacks = {
|
|
57
|
-
onUri: handleUri,
|
|
58
|
-
onStatus: handleStatus,
|
|
59
|
-
};
|
|
60
|
-
connect(callbacks)
|
|
61
|
-
.then((result) => onComplete(result))
|
|
62
|
-
.catch((err: unknown) => onError(err instanceof Error ? err.message : String(err)));
|
|
63
|
-
}, [connect, handleUri, handleStatus, onComplete, onError]);
|
|
64
|
-
|
|
65
|
-
const statusIcon = stage === "error" ? "✗" : stage === "ready" ? "✓" : "◈";
|
|
66
|
-
const statusColor = stage === "error" ? "red" : stage === "ready" ? "green" : "cyan";
|
|
67
|
-
|
|
68
|
-
const statusMessages: Record<WCStage, string> = {
|
|
69
|
-
connecting: "Waiting for wallet approval...",
|
|
70
|
-
connected: `Connected: ${account ?? ""}`,
|
|
71
|
-
"chain-switching": `Switching chain${detail ? `: ${detail}` : ""}...`,
|
|
72
|
-
ready: `Ready — ${account ?? ""}`,
|
|
73
|
-
error: detail || "Connection failed",
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<Box flexDirection="column" paddingLeft={1}>
|
|
78
|
-
<Text color="cyan" bold>WalletConnect Pairing</Text>
|
|
79
|
-
<Text> </Text>
|
|
80
|
-
|
|
81
|
-
{/* Status */}
|
|
82
|
-
<Box>
|
|
83
|
-
<Text color={statusColor}>{statusIcon} </Text>
|
|
84
|
-
<Text>{statusMessages[stage]}</Text>
|
|
85
|
-
</Box>
|
|
86
|
-
<Text> </Text>
|
|
87
|
-
|
|
88
|
-
{/* Deep links */}
|
|
89
|
-
{uri && stage === "connecting" && (
|
|
90
|
-
<>
|
|
91
|
-
<Text dimColor>Tap a link for your wallet app:</Text>
|
|
92
|
-
<Text> </Text>
|
|
93
|
-
{Object.entries(links).map(([name, link]) => (
|
|
94
|
-
<Box key={name} flexDirection="column">
|
|
95
|
-
<Text color="white">{name}:</Text>
|
|
96
|
-
<Text dimColor>{link}</Text>
|
|
97
|
-
<Text> </Text>
|
|
98
|
-
</Box>
|
|
99
|
-
))}
|
|
100
|
-
|
|
101
|
-
{/* QR code */}
|
|
102
|
-
{qrLines.length > 0 && (
|
|
103
|
-
<>
|
|
104
|
-
<Text dimColor>Or scan QR:</Text>
|
|
105
|
-
{qrLines.map((line, i) => (
|
|
106
|
-
<Text key={i}>{line}</Text>
|
|
107
|
-
))}
|
|
108
|
-
</>
|
|
109
|
-
)}
|
|
110
|
-
</>
|
|
111
|
-
)}
|
|
112
|
-
</Box>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text, useFocus, useInput } from "ink";
|
|
3
|
-
|
|
4
|
-
export interface ButtonProps {
|
|
5
|
-
label: string;
|
|
6
|
-
onPress: () => void;
|
|
7
|
-
variant?: "primary" | "danger" | "dim";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const VARIANT_COLORS: Record<string, string> = {
|
|
11
|
-
primary: "cyan",
|
|
12
|
-
danger: "red",
|
|
13
|
-
dim: "gray",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function Button({ label, onPress, variant = "primary" }: ButtonProps) {
|
|
17
|
-
const { isFocused } = useFocus();
|
|
18
|
-
|
|
19
|
-
useInput(
|
|
20
|
-
(_input, key) => {
|
|
21
|
-
if (key.return) {
|
|
22
|
-
onPress();
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{ isActive: isFocused }
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const color = isFocused ? VARIANT_COLORS[variant] ?? "cyan" : "white";
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Box>
|
|
32
|
-
<Text color={color} bold={isFocused}>
|
|
33
|
-
{isFocused ? "▸ " : " "}
|
|
34
|
-
{label}
|
|
35
|
-
</Text>
|
|
36
|
-
</Box>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { StepSpinner } from "./StepSpinner.js";
|
|
4
|
-
import type { StepStatus } from "./StepSpinner.js";
|
|
5
|
-
|
|
6
|
-
export interface CeremonyStep {
|
|
7
|
-
label: string;
|
|
8
|
-
status: StepStatus;
|
|
9
|
-
detail?: string;
|
|
10
|
-
error?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CeremonyViewProps {
|
|
14
|
-
title: string;
|
|
15
|
-
steps: CeremonyStep[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function CeremonyView({ title, steps }: CeremonyViewProps) {
|
|
19
|
-
return (
|
|
20
|
-
<Box flexDirection="column">
|
|
21
|
-
<Box marginBottom={1}>
|
|
22
|
-
<Text bold color="cyan">
|
|
23
|
-
◈ {title}
|
|
24
|
-
</Text>
|
|
25
|
-
</Box>
|
|
26
|
-
{steps.map((step, i) => (
|
|
27
|
-
<StepSpinner
|
|
28
|
-
key={i}
|
|
29
|
-
step={i + 1}
|
|
30
|
-
total={steps.length}
|
|
31
|
-
label={step.label}
|
|
32
|
-
status={step.status}
|
|
33
|
-
detail={step.detail}
|
|
34
|
-
error={step.error}
|
|
35
|
-
/>
|
|
36
|
-
))}
|
|
37
|
-
</Box>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
export interface CompletionDropdownProps {
|
|
5
|
-
candidates: string[];
|
|
6
|
-
selectedIndex: number;
|
|
7
|
-
visible: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const MAX_VISIBLE = 8;
|
|
11
|
-
|
|
12
|
-
export function CompletionDropdown({
|
|
13
|
-
candidates,
|
|
14
|
-
selectedIndex,
|
|
15
|
-
visible,
|
|
16
|
-
}: CompletionDropdownProps) {
|
|
17
|
-
if (!visible || candidates.length === 0) return null;
|
|
18
|
-
|
|
19
|
-
// Window the list if there are too many candidates
|
|
20
|
-
let startIdx = 0;
|
|
21
|
-
if (candidates.length > MAX_VISIBLE) {
|
|
22
|
-
startIdx = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE / 2));
|
|
23
|
-
startIdx = Math.min(startIdx, candidates.length - MAX_VISIBLE);
|
|
24
|
-
}
|
|
25
|
-
const visibleCandidates = candidates.slice(
|
|
26
|
-
startIdx,
|
|
27
|
-
startIdx + MAX_VISIBLE
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<Box flexDirection="column" marginLeft={4}>
|
|
32
|
-
<Box>
|
|
33
|
-
<Text dimColor>{"┌─ completions ─"}</Text>
|
|
34
|
-
</Box>
|
|
35
|
-
{visibleCandidates.map((candidate, i) => {
|
|
36
|
-
const actualIdx = startIdx + i;
|
|
37
|
-
const isSelected = actualIdx === selectedIndex;
|
|
38
|
-
return (
|
|
39
|
-
<Box key={candidate}>
|
|
40
|
-
<Text dimColor>{"│"}</Text>
|
|
41
|
-
<Text color={isSelected ? "cyan" : "white"} bold={isSelected}>
|
|
42
|
-
{isSelected ? " ▸ " : " "}
|
|
43
|
-
{candidate}
|
|
44
|
-
</Text>
|
|
45
|
-
</Box>
|
|
46
|
-
);
|
|
47
|
-
})}
|
|
48
|
-
<Box>
|
|
49
|
-
<Text dimColor>{"└─"}</Text>
|
|
50
|
-
{candidates.length > MAX_VISIBLE && (
|
|
51
|
-
<Text dimColor>{" "}({candidates.length} total)</Text>
|
|
52
|
-
)}
|
|
53
|
-
</Box>
|
|
54
|
-
</Box>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { Button } from "./Button.js";
|
|
4
|
-
|
|
5
|
-
export interface ConfirmPromptProps {
|
|
6
|
-
message: string;
|
|
7
|
-
onConfirm: () => void;
|
|
8
|
-
onCancel: () => void;
|
|
9
|
-
confirmLabel?: string;
|
|
10
|
-
cancelLabel?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function ConfirmPrompt({
|
|
14
|
-
message,
|
|
15
|
-
onConfirm,
|
|
16
|
-
onCancel,
|
|
17
|
-
confirmLabel = "Confirm",
|
|
18
|
-
cancelLabel = "Cancel",
|
|
19
|
-
}: ConfirmPromptProps) {
|
|
20
|
-
return (
|
|
21
|
-
<Box flexDirection="column">
|
|
22
|
-
<Box marginBottom={1}>
|
|
23
|
-
<Text bold color="cyan">
|
|
24
|
-
◈ {message}
|
|
25
|
-
</Text>
|
|
26
|
-
</Box>
|
|
27
|
-
<Box gap={2}>
|
|
28
|
-
<Button label={confirmLabel} onPress={onConfirm} variant="primary" />
|
|
29
|
-
<Button label={cancelLabel} onPress={onCancel} variant="dim" />
|
|
30
|
-
</Box>
|
|
31
|
-
<Box marginTop={1}>
|
|
32
|
-
<Text dimColor>Tab to switch · Enter to select</Text>
|
|
33
|
-
</Box>
|
|
34
|
-
</Box>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Text, useInput } from "ink";
|
|
3
|
-
|
|
4
|
-
interface CustomTextInputProps {
|
|
5
|
-
value: string;
|
|
6
|
-
onChange: (value: string) => void;
|
|
7
|
-
onSubmit?: (value: string) => void;
|
|
8
|
-
focus?: boolean;
|
|
9
|
-
/** When true, Enter key is NOT handled — let parent handle it (e.g. dropdown selection) */
|
|
10
|
-
suppressEnter?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Minimal text input that does NOT intercept Tab, Up, Down, Escape, or Ctrl+C,
|
|
15
|
-
* allowing parent useInput handlers to receive those keys.
|
|
16
|
-
*/
|
|
17
|
-
export function CustomTextInput({
|
|
18
|
-
value,
|
|
19
|
-
onChange,
|
|
20
|
-
onSubmit,
|
|
21
|
-
focus = true,
|
|
22
|
-
suppressEnter = false,
|
|
23
|
-
}: CustomTextInputProps) {
|
|
24
|
-
const [cursorPos, setCursorPos] = useState(value.length);
|
|
25
|
-
const [cursorVisible, setCursorVisible] = useState(true);
|
|
26
|
-
|
|
27
|
-
// Keep cursor within bounds when value changes externally
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
setCursorPos((pos) => Math.min(pos, value.length));
|
|
30
|
-
}, [value]);
|
|
31
|
-
|
|
32
|
-
// Blink cursor
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (!focus) return;
|
|
35
|
-
const timer = setInterval(() => {
|
|
36
|
-
setCursorVisible((v) => !v);
|
|
37
|
-
}, 530);
|
|
38
|
-
return () => clearInterval(timer);
|
|
39
|
-
}, [focus]);
|
|
40
|
-
|
|
41
|
-
useInput(
|
|
42
|
-
(input, key) => {
|
|
43
|
-
// Keys we explicitly do NOT handle — let parent see them:
|
|
44
|
-
// Tab, Up, Down, Escape, Ctrl+C
|
|
45
|
-
if (key.tab || key.upArrow || key.downArrow || key.escape) return;
|
|
46
|
-
if (input === "\x03") return; // Ctrl+C
|
|
47
|
-
|
|
48
|
-
// Enter/Return — submit (unless suppressed for dropdown selection)
|
|
49
|
-
if (key.return) {
|
|
50
|
-
if (!suppressEnter) {
|
|
51
|
-
onSubmit?.(value);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Backspace
|
|
57
|
-
if (key.backspace || key.delete) {
|
|
58
|
-
if (cursorPos > 0) {
|
|
59
|
-
const next = value.slice(0, cursorPos - 1) + value.slice(cursorPos);
|
|
60
|
-
setCursorPos(cursorPos - 1);
|
|
61
|
-
onChange(next);
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Left arrow
|
|
67
|
-
if (key.leftArrow) {
|
|
68
|
-
setCursorPos((p) => Math.max(0, p - 1));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Right arrow
|
|
73
|
-
if (key.rightArrow) {
|
|
74
|
-
setCursorPos((p) => Math.min(value.length, p + 1));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Home (Ctrl+A)
|
|
79
|
-
if (input === "\x01") {
|
|
80
|
-
setCursorPos(0);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// End (Ctrl+E)
|
|
85
|
-
if (input === "\x05") {
|
|
86
|
-
setCursorPos(value.length);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Ctrl+U — clear line
|
|
91
|
-
if (input === "\x15") {
|
|
92
|
-
setCursorPos(0);
|
|
93
|
-
onChange("");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Ctrl+K — kill to end of line
|
|
98
|
-
if (input === "\x0B") {
|
|
99
|
-
onChange(value.slice(0, cursorPos));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Ignore other control characters
|
|
104
|
-
if (input.length > 0 && input.charCodeAt(0) < 32) return;
|
|
105
|
-
|
|
106
|
-
// Regular character input (including paste)
|
|
107
|
-
if (input.length > 0) {
|
|
108
|
-
const next =
|
|
109
|
-
value.slice(0, cursorPos) + input + value.slice(cursorPos);
|
|
110
|
-
setCursorPos(cursorPos + input.length);
|
|
111
|
-
onChange(next);
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
{ isActive: focus }
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
if (!focus) {
|
|
118
|
-
return <Text dimColor>{value}</Text>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const before = value.slice(0, cursorPos);
|
|
122
|
-
const cursorChar = cursorPos < value.length ? value[cursorPos] : " ";
|
|
123
|
-
const after = value.slice(cursorPos + 1);
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<Text>
|
|
127
|
-
{before}
|
|
128
|
-
<Text inverse={cursorVisible}>{cursorChar}</Text>
|
|
129
|
-
{after}
|
|
130
|
-
</Text>
|
|
131
|
-
);
|
|
132
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Box, Text, useInput } from "ink";
|
|
3
|
-
|
|
4
|
-
export interface Column {
|
|
5
|
-
header: string;
|
|
6
|
-
key: string;
|
|
7
|
-
width?: number;
|
|
8
|
-
align?: "left" | "right";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface InteractiveTableProps {
|
|
12
|
-
columns: Column[];
|
|
13
|
-
rows: Record<string, string>[];
|
|
14
|
-
onSelect?: (row: Record<string, string>, index: number) => void;
|
|
15
|
-
selectedIndex?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const MAX_VISIBLE_ROWS = 15;
|
|
19
|
-
|
|
20
|
-
export function InteractiveTable({
|
|
21
|
-
columns,
|
|
22
|
-
rows,
|
|
23
|
-
onSelect,
|
|
24
|
-
selectedIndex: controlledIdx,
|
|
25
|
-
}: InteractiveTableProps) {
|
|
26
|
-
const [internalIdx, setInternalIdx] = useState(0);
|
|
27
|
-
const selectedIndex = controlledIdx ?? internalIdx;
|
|
28
|
-
|
|
29
|
-
useInput((_input, key) => {
|
|
30
|
-
if (key.upArrow) {
|
|
31
|
-
setInternalIdx((i) => Math.max(0, i - 1));
|
|
32
|
-
}
|
|
33
|
-
if (key.downArrow) {
|
|
34
|
-
setInternalIdx((i) => Math.min(rows.length - 1, i + 1));
|
|
35
|
-
}
|
|
36
|
-
if (key.return && onSelect && rows[selectedIndex]) {
|
|
37
|
-
onSelect(rows[selectedIndex], selectedIndex);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Compute column widths
|
|
42
|
-
const colWidths = columns.map((col) => {
|
|
43
|
-
if (col.width) return col.width;
|
|
44
|
-
let max = col.header.length;
|
|
45
|
-
for (const row of rows) {
|
|
46
|
-
const val = row[col.key] ?? "";
|
|
47
|
-
if (val.length > max) max = val.length;
|
|
48
|
-
}
|
|
49
|
-
return Math.min(max + 2, 30);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const pad = (text: string, width: number, align: "left" | "right" = "left"): string => {
|
|
53
|
-
const truncated = text.length > width ? text.slice(0, width - 1) + "…" : text;
|
|
54
|
-
if (align === "right") return truncated.padStart(width);
|
|
55
|
-
return truncated.padEnd(width);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Window visible rows
|
|
59
|
-
let startRow = 0;
|
|
60
|
-
if (rows.length > MAX_VISIBLE_ROWS) {
|
|
61
|
-
startRow = Math.max(0, selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2));
|
|
62
|
-
startRow = Math.min(startRow, rows.length - MAX_VISIBLE_ROWS);
|
|
63
|
-
}
|
|
64
|
-
const visibleRows = rows.slice(startRow, startRow + MAX_VISIBLE_ROWS);
|
|
65
|
-
|
|
66
|
-
// Header
|
|
67
|
-
const headerLine = columns
|
|
68
|
-
.map((col, i) => pad(col.header, colWidths[i], col.align))
|
|
69
|
-
.join(" ");
|
|
70
|
-
const separatorLine = "─".repeat(headerLine.length);
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<Box flexDirection="column">
|
|
74
|
-
<Box>
|
|
75
|
-
<Text bold color="white">{" "}{headerLine}</Text>
|
|
76
|
-
</Box>
|
|
77
|
-
<Box>
|
|
78
|
-
<Text dimColor> {separatorLine}</Text>
|
|
79
|
-
</Box>
|
|
80
|
-
{visibleRows.map((row, vi) => {
|
|
81
|
-
const actualIdx = startRow + vi;
|
|
82
|
-
const isSelected = actualIdx === selectedIndex;
|
|
83
|
-
const line = columns
|
|
84
|
-
.map((col, i) => pad(row[col.key] ?? "", colWidths[i], col.align))
|
|
85
|
-
.join(" ");
|
|
86
|
-
return (
|
|
87
|
-
<Box key={actualIdx}>
|
|
88
|
-
<Text color={isSelected ? "cyan" : "white"} bold={isSelected}>
|
|
89
|
-
{isSelected ? "▸" : " "} {line}
|
|
90
|
-
</Text>
|
|
91
|
-
</Box>
|
|
92
|
-
);
|
|
93
|
-
})}
|
|
94
|
-
{rows.length > MAX_VISIBLE_ROWS && (
|
|
95
|
-
<Box>
|
|
96
|
-
<Text dimColor>{" "}({rows.length} rows · ↑↓ navigate · Enter select)</Text>
|
|
97
|
-
</Box>
|
|
98
|
-
)}
|
|
99
|
-
{rows.length <= MAX_VISIBLE_ROWS && rows.length > 0 && (
|
|
100
|
-
<Box>
|
|
101
|
-
<Text dimColor> (↑↓ navigate · Enter select)</Text>
|
|
102
|
-
</Box>
|
|
103
|
-
)}
|
|
104
|
-
</Box>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
const SPINNER_FRAMES = ["◈", "◇", "◆", "◇"];
|
|
5
|
-
const SPINNER_INTERVAL = 120;
|
|
6
|
-
|
|
7
|
-
export type StepStatus = "pending" | "running" | "done" | "error";
|
|
8
|
-
|
|
9
|
-
export interface StepSpinnerProps {
|
|
10
|
-
step: number;
|
|
11
|
-
total: number;
|
|
12
|
-
label: string;
|
|
13
|
-
status: StepStatus;
|
|
14
|
-
detail?: string;
|
|
15
|
-
error?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function StepSpinner({
|
|
19
|
-
step,
|
|
20
|
-
total,
|
|
21
|
-
label,
|
|
22
|
-
status,
|
|
23
|
-
detail,
|
|
24
|
-
error,
|
|
25
|
-
}: StepSpinnerProps) {
|
|
26
|
-
const [frame, setFrame] = useState(0);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
if (status !== "running") return;
|
|
30
|
-
const timer = setInterval(() => {
|
|
31
|
-
setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
|
|
32
|
-
}, SPINNER_INTERVAL);
|
|
33
|
-
return () => clearInterval(timer);
|
|
34
|
-
}, [status]);
|
|
35
|
-
|
|
36
|
-
const prefix = `Step ${step}/${total}`;
|
|
37
|
-
|
|
38
|
-
if (status === "pending") {
|
|
39
|
-
return (
|
|
40
|
-
<Box>
|
|
41
|
-
<Text dimColor> {prefix} — {label}</Text>
|
|
42
|
-
</Box>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (status === "running") {
|
|
47
|
-
return (
|
|
48
|
-
<Box flexDirection="column">
|
|
49
|
-
<Box>
|
|
50
|
-
<Text color="cyan"> {SPINNER_FRAMES[frame]} {prefix} — {label}...</Text>
|
|
51
|
-
</Box>
|
|
52
|
-
</Box>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (status === "done") {
|
|
57
|
-
return (
|
|
58
|
-
<Box flexDirection="column">
|
|
59
|
-
<Box>
|
|
60
|
-
<Text color="green"> ✓ {prefix} — {label}</Text>
|
|
61
|
-
</Box>
|
|
62
|
-
{detail && (
|
|
63
|
-
<Box>
|
|
64
|
-
<Text dimColor> └ {detail}</Text>
|
|
65
|
-
</Box>
|
|
66
|
-
)}
|
|
67
|
-
</Box>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// error
|
|
72
|
-
return (
|
|
73
|
-
<Box flexDirection="column">
|
|
74
|
-
<Box>
|
|
75
|
-
<Text color="red"> ✗ {prefix} — {label}</Text>
|
|
76
|
-
</Box>
|
|
77
|
-
{error && (
|
|
78
|
-
<Box>
|
|
79
|
-
<Text color="red"> └ {error}</Text>
|
|
80
|
-
</Box>
|
|
81
|
-
)}
|
|
82
|
-
</Box>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
export type ToastVariant = "info" | "success" | "warning" | "error";
|
|
5
|
-
|
|
6
|
-
export interface ToastData {
|
|
7
|
-
id: string;
|
|
8
|
-
message: string;
|
|
9
|
-
variant: ToastVariant;
|
|
10
|
-
duration?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ToastProps {
|
|
14
|
-
toast: ToastData;
|
|
15
|
-
onDismiss: (id: string) => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const VARIANT_CONFIG: Record<ToastVariant, { icon: string; color: string }> = {
|
|
19
|
-
info: { icon: "◈", color: "cyan" },
|
|
20
|
-
success: { icon: "✓", color: "green" },
|
|
21
|
-
warning: { icon: "⚠", color: "yellow" },
|
|
22
|
-
error: { icon: "✗", color: "red" },
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export function Toast({ toast, onDismiss }: ToastProps) {
|
|
26
|
-
const { icon, color } = VARIANT_CONFIG[toast.variant];
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
const timer = setTimeout(() => {
|
|
30
|
-
onDismiss(toast.id);
|
|
31
|
-
}, toast.duration ?? 5000);
|
|
32
|
-
return () => clearTimeout(timer);
|
|
33
|
-
}, [toast.id, toast.duration, onDismiss]);
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Box>
|
|
37
|
-
<Text color={color}>
|
|
38
|
-
{icon} {toast.message}
|
|
39
|
-
</Text>
|
|
40
|
-
</Box>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ToastContainerProps {
|
|
45
|
-
toasts: ToastData[];
|
|
46
|
-
onDismiss: (id: string) => void;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function ToastContainer({ toasts, onDismiss }: ToastContainerProps) {
|
|
50
|
-
if (toasts.length === 0) return null;
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<Box flexDirection="column">
|
|
54
|
-
{toasts.map((toast) => (
|
|
55
|
-
<Toast key={toast.id} toast={toast} onDismiss={onDismiss} />
|
|
56
|
-
))}
|
|
57
|
-
</Box>
|
|
58
|
-
);
|
|
59
|
-
}
|