{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "defi-swap",
  "type": "registry:ui",
  "description": "Swap component for displaying real-time swap data",
  "files": [
    {
      "path": "packages/defi/swap.tsx",
      "content": "\"use client\";\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card } from \"@/components/ui/card\";\nimport {\n    Select,\n    SelectContent,\n    SelectItem,\n    SelectTrigger,\n    SelectValue,\n} from \"@/components/ui/select\";\nimport { RiSettings4Line, RiArrowDownLine } from \"@remixicon/react\";\nimport { I18nProvider, Input, Label, NumberField } from \"react-aria-components\";\nimport { cn } from \"@repo/shadcn-ui/lib/utils\";\nimport { useState } from \"react\";\n\ninterface ConverterFieldProps {\n    className?: string;\n    isLast?: boolean;\n    amount: number;\n    onAmountChange: (value: number) => void;\n    balance: string;\n    selectedCoin: string;\n    onSelectCoin: (value: string) => void;\n    onMax?: () => void;\n    coins: {\n        id: string;\n        name: string;\n        image: string;\n    }[];\n}\n\nfunction ConverterField({\n    className,\n    isLast,\n    amount,\n    onAmountChange,\n    balance,\n    selectedCoin,\n    onSelectCoin,\n    onMax,\n    coins,\n}: ConverterFieldProps) {\n    return (\n        <>\n            {/* Arrow */}\n            {isLast && (\n                <div\n                    className=\"size-6 border z-10 rounded-full border-primary flex flex-col items-center justify-center bg-linear-to-b from-primary to-primary-to inset-shadow-[0_1px_rgb(255_255_255/0.15)] absolute top-1/2 -translate-y-1/2\"\n                    aria-hidden=\"true\"\n                >\n                    <RiArrowDownLine className=\"text-primary-foreground\" size={20} />\n                </div>\n            )}\n            {/* Converter */}\n            <Card\n                className={cn(\n                    \"relative w-full flex-row items-center justify-between gap-2 p-4 dark:bg-card/64 border-2 border-border\",\n                    isLast\n                        ? \"[mask-image:radial-gradient(ellipse_26px_24px_at_50%_0%,transparent_0,_transparent_24px,_black_25px)]\"\n                        : \"[mask-image:radial-gradient(ellipse_26px_24px_at_50%_100%,transparent_0,_transparent_24px,_black_25px)]\",\n                    className,\n                )}\n            >\n                {/* Arrow */}\n                {isLast && (\n                    <div\n                        className=\"absolute -top-px left-1/2 -translate-x-1/2 w-[50px] h-[25px] rounded-b-full border-b border-x border-white/15\"\n                        aria-hidden=\"true\"\n                    ></div>\n                )}\n                {/* Amount */}\n                <div className=\"grow\">\n                    <I18nProvider locale=\"en-US\">\n                        <NumberField\n                            value={amount}\n                            onChange={(v) => onAmountChange(v ?? 0)}\n                            minValue={0}\n                            formatOptions={{\n                                minimumFractionDigits: 1,\n                                maximumFractionDigits: 2,\n                                useGrouping: true,\n                            }}\n                        >\n                            <Label className=\"sr-only\">Amount</Label>\n                            <Input className=\"w-full max-w-40 text-2xl font-semibold bg-transparent focus-visible:outline-none py-0.5 px-1 -ml-1 mb-0.5 focus:bg-card/64 rounded-lg appearance-none\" />\n                        </NumberField>\n                    </I18nProvider>\n                    <div className=\"text-xs text-muted-foreground flex items-center justify-between gap-3\">\n                        <div\n                            className=\"flex items-center gap-2 cursor-pointer\"\n                            onClick={onMax}\n                        >\n                            <span className=\"text-muted-foreground/70\">Balance: </span>\n                            {balance}\n                        </div>\n                        {/* {onMax && (\n                            <Button\n                                type=\"button\"\n                                variant=\"ghost\"\n                                size=\"sm\"\n                                className=\"h-6 px-2 py-0 text-[10px] uppercase tracking-wide\"\n                                onClick={onMax}\n                            >\n                                MAX\n                            </Button>\n                        )} */}\n                    </div>\n                </div>\n                {/* Coin selector */}\n                <div className=\"flex items-center gap-2\">\n                    <Select value={selectedCoin} onValueChange={onSelectCoin}>\n                        <SelectTrigger className=\"p-1 pr-2 h-8 rounded-full [&>span_svg]:text-muted-foreground/80 [&>span]:flex [&>span]:items-center [&>span]:gap-2 [&>span_svg]:shrink-0 border-0 bg-card/64 hover:bg-card/80 shadow-lg inset-shadow-[0_1px_rgb(255_255_255/0.15)]\">\n                            <SelectValue placeholder=\"Select coin\" />\n                        </SelectTrigger>\n                        <SelectContent\n                            className=\"dark bg-muted border-none shadow-black/10 inset-shadow-[0_1px_rgb(255_255_255/0.15)] [&_*[role=option]>span>svg]:text-muted-foreground/80 [&_*[role=option]]:ps-2 [&_*[role=option]]:pe-8 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:end-2 [&_*[role=option]>span]:flex [&_*[role=option]>span]:items-center [&_*[role=option]>span]:gap-2 [&_*[role=option]>span>svg]:shrink-0\"\n                            align=\"center\"\n                        >\n                            {coins.map((coin) => (\n                                <SelectItem key={coin.id} value={coin.id}>\n                                    <img\n                                        className=\"shrink-0 rounded-full shadow-[0px_0px_0px_1px_rgba(0,0,0,0.04),0_1px_1px_rgba(0,0,0,0.05),0_2px_2px_rgba(0,0,0,0.05),0_2px_4px_rgba(0,0,0,0.05)]\"\n                                        src={coin.image}\n                                        width={24}\n                                        height={24}\n                                        alt={coin.name}\n                                    />\n                                    <span className=\"truncate uppercase text-xs font-medium\">\n                                        {coin.name}\n                                    </span>\n                                </SelectItem>\n                            ))}\n                        </SelectContent>\n                    </Select>\n                </div>\n            </Card>\n        </>\n    );\n}\n\nexport function Converter() {\n    const coins = [\n        {\n            id: \"1\",\n            name: \"Ark\",\n            image:\n                \"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/exp4/coin-01-sm-dark_hkrvvm.svg\",\n        },\n        {\n            id: \"2\",\n            name: \"Tok\",\n            image:\n                \"https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/exp4/coin-02-sm-dark_iqldgv.svg\",\n        },\n    ];\n\n    function ConverterContent() {\n        const [fromCoinId, setFromCoinId] = useState<string>(\"2\");\n        const [toCoinId, setToCoinId] = useState<string>(\"1\");\n        const balances: Record<string, string> = {\n            \"1\": \"54,579\", // Ark\n            \"2\": \"12,234.2\", // Tok\n        };\n\n        // Simple pair rate helper. Example baseline: 1 ARK = 25.00 TOK\n        const getRate = (fromId: string, toId: string): number => {\n            if (fromId === toId) return 1;\n            const ARK = \"1\";\n            const TOK = \"2\";\n            const arkToTok = 25.00; // 1 ARK -> 25.00 TOK\n            if (fromId === ARK && toId === TOK) return arkToTok;\n            if (fromId === TOK && toId === ARK) return 1 / arkToTok;\n            return 1; // fallback\n        };\n\n        const round2 = (n: number) => Math.max(0, Math.round((n + Number.EPSILON) * 100) / 100);\n\n        // Amounts and last edited side\n        const [fromAmount, setFromAmount] = useState<number>(1);\n        const [toAmount, setToAmount] = useState<number>(() => round2(1 * getRate(\"2\", \"1\")));\n        const [lastEdited, setLastEdited] = useState<\"from\" | \"to\">(\"from\");\n\n        const handleFromChange = (value: string) => {\n            const previousFrom = fromCoinId;\n            setFromCoinId(value);\n            if (value === toCoinId) {\n                const fallback = coins.find((c) => c.id !== value)?.id ?? toCoinId;\n                setToCoinId(previousFrom !== value ? previousFrom : fallback);\n            }\n            // Recalculate the opposite amount based on who was last edited to preserve user intent\n            const rate = getRate(value, toCoinId);\n            if (lastEdited === \"from\") {\n                setToAmount((prev) => round2(fromAmount * rate));\n            } else {\n                // last edited is \"to\", keep toAmount and back-calc from\n                setFromAmount((prev) => round2(toAmount / rate));\n            }\n        };\n\n        const handleToChange = (value: string) => {\n            const previousTo = toCoinId;\n            setToCoinId(value);\n            if (value === fromCoinId) {\n                const fallback = coins.find((c) => c.id !== value)?.id ?? fromCoinId;\n                setFromCoinId(previousTo !== value ? previousTo : fallback);\n            }\n            const rate = getRate(fromCoinId, value);\n            if (lastEdited === \"from\") {\n                setToAmount((prev) => round2(fromAmount * rate));\n            } else {\n                setFromAmount((prev) => round2(toAmount / rate));\n            }\n        };\n\n        // Handlers for amount edits\n        const handleFromAmountChange = (val: number) => {\n            const rate = getRate(fromCoinId, toCoinId);\n            setFromAmount(val);\n            setToAmount(round2(val * rate));\n            setLastEdited(\"from\");\n        };\n\n        const handleToAmountChange = (val: number) => {\n            const rate = getRate(fromCoinId, toCoinId);\n            setToAmount(val);\n            setFromAmount(val === 0 ? 0 : round2(val / rate));\n            setLastEdited(\"to\");\n        };\n\n        // MAX handler for the input (from) field\n        const parseBalance = (s: string) => {\n            // Remove commas and non-numeric except dot\n            const cleaned = (s ?? \"0\").replace(/[^0-9.]/g, \"\");\n            const n = parseFloat(cleaned);\n            return Number.isFinite(n) ? n : 0;\n        };\n        const handleMax = () => {\n            const full = parseBalance(balances[fromCoinId] ?? \"0\");\n            handleFromAmountChange(full);\n        };\n\n        // Pricing and summary calculations\n        const usdPrices: Record<string, number> = {\n            \"1\": 25, // ARK -> $25.00 (demo)\n            \"2\": 1, // TOK -> $1.00 (demo)\n        };\n        const formatUSD = (n: number) =>\n            new Intl.NumberFormat(\"en-US\", { style: \"currency\", currency: \"USD\", maximumFractionDigits: 2 }).format(n);\n        const networkFeeUSD = 3.20;\n        const txValueUSD = (usdPrices[toCoinId] ?? 0) * toAmount;\n        const orderNetUSD = txValueUSD + networkFeeUSD;\n\n        return (\n            <>\n                <div className=\"relative flex flex-col items-center gap-1 mb-4\">\n                    <ConverterField\n                        amount={fromAmount}\n                        onAmountChange={handleFromAmountChange}\n                        balance={balances[fromCoinId] ?? \"0\"}\n                        selectedCoin={fromCoinId}\n                        onSelectCoin={handleFromChange}\n                        onMax={handleMax}\n                        coins={coins}\n                    />\n                    <ConverterField\n                        isLast\n                        amount={toAmount}\n                        onAmountChange={handleToAmountChange}\n                        balance={balances[toCoinId] ?? \"0\"}\n                        selectedCoin={toCoinId}\n                        onSelectCoin={handleToChange}\n                        coins={coins}\n                    />\n                </div>\n                {/* Summary */}\n                <div className=\"mb-2 ps-3 uppercase text-muted-foreground/50 text-xs font-medium rounded-md\">\n                    Summary\n                </div>\n                <Card className=\"p-4 gap-0 rounded-md\">\n                    <ul className=\"text-sm\">\n                        <li className=\"flex items-center justify-between pb-3 mb-3 border-b border-card/50\">\n                            <span className=\"text-muted-foreground\">Transaction Value</span>\n                            <span className=\"font-medium\">{formatUSD(txValueUSD)}</span>\n                        </li>\n                        <li className=\"flex items-center justify-between pb-3 mb-3 border-b border-card/50\">\n                            <span className=\"text-muted-foreground\">Network Fees</span>\n                            <span className=\"font-medium\">{formatUSD(networkFeeUSD)}</span>\n                        </li>\n                        <li className=\"flex items-center justify-between pb-3 mb-3 border-b border-card/50\">\n                            <span className=\"text-muted-foreground\">Order Net</span>\n                            <span className=\"font-medium\">{formatUSD(orderNetUSD)}</span>\n                        </li>\n                    </ul>\n                    <Button size=\"lg\" className=\"w-full\">\n                        Confirm\n                    </Button>\n                    <div className=\"text-xs text-center uppercase mt-3\">\n                        {coins.find((c) => c.id === fromCoinId)?.name} <span className=\"text-muted-foreground\">=</span> 1,574.04{\" \"}\n                        {coins.find((c) => c.id === toCoinId)?.name}\n                    </div>\n                </Card>\n            </>\n        );\n    }\n\n    return (\n        <Tabs defaultValue=\"tab-1\" className=\"flex-1 gap-5\">\n            <div className=\"flex items-center gap-2 justify-end\">\n                <Button\n                    size=\"icon\"\n                    variant=\"ghost\"\n                    className=\"size-8 shrink-0 text-muted-foreground hover:text-foreground/80\"\n                >\n                    <span className=\"sr-only\">Settings</span>\n                    <RiSettings4Line className=\"size-5\" size={20} aria-hidden=\"true\" />\n                </Button>\n            </div>\n            <div className=\"dark bg-background dark:bg-secondary/64 rounded-2xl p-2\">\n                <ConverterContent />\n            </div>\n        </Tabs>\n    );\n}",
      "type": "registry:component"
    }
  ]
}