{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "currency-converter-card-shadcnui", "type": "registry:component", "title": "Currency Converter Card", "description": "Finance conversion widget with animated inputs, simulated exchange updates, and contextual feedback", "registryDependencies": [ "button", "card" ], "dependencies": [ "framer-motion", "react" ], "files": [ { "path": "@uitripled/react-shadcn/src/components/sections/currency-converter-card.tsx", "content": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { ArrowLeftRight, TrendingUp } from \"lucide-react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\ntype Currency = {\n code: string;\n name: string;\n symbol: string;\n};\n\ntype CurrencyCode = (typeof CURRENCIES)[number][\"code\"];\n\nconst CURRENCIES: Currency[] = [\n { code: \"USD\", name: \"US Dollar\", symbol: \"$\" },\n { code: \"EUR\", name: \"Euro\", symbol: \"€\" },\n { code: \"GBP\", name: \"British Pound\", symbol: \"£\" },\n { code: \"JPY\", name: \"Japanese Yen\", symbol: \"¥\" },\n { code: \"AUD\", name: \"Australian Dollar\", symbol: \"A$\" },\n { code: \"CAD\", name: \"Canadian Dollar\", symbol: \"C$\" },\n { code: \"CHF\", name: \"Swiss Franc\", symbol: \"CHF\" },\n { code: \"CNY\", name: \"Chinese Yuan\", symbol: \"¥\" },\n { code: \"INR\", name: \"Indian Rupee\", symbol: \"₹\" },\n { code: \"MXN\", name: \"Mexican Peso\", symbol: \"$\" },\n];\n\nconst BASE_INDEX: Record = {\n USD: 1,\n EUR: 0.92,\n GBP: 0.78,\n JPY: 147.42,\n AUD: 1.5,\n CAD: 1.36,\n CHF: 0.88,\n CNY: 7.11,\n INR: 83.24,\n MXN: 17.12,\n};\n\nexport function CurrencyConverterCard() {\n const [amount, setAmount] = useState(\"100\");\n const [fromCurrency, setFromCurrency] = useState(\"USD\");\n const [toCurrency, setToCurrency] = useState(\"EUR\");\n const [result, setResult] = useState(null);\n const [rate, setRate] = useState(null);\n const [loading, setLoading] = useState(false);\n const [isFlipped, setIsFlipped] = useState(false);\n const [error, setError] = useState(\"\");\n\n useEffect(() => {\n const trimmed = amount.trim();\n\n if (!trimmed) {\n setLoading(false);\n setError(\"\");\n setRate(null);\n setResult(null);\n return;\n }\n\n const numericAmount = Number(trimmed);\n\n if (Number.isNaN(numericAmount)) {\n setLoading(false);\n setError(\"Enter a valid amount\");\n setRate(null);\n setResult(null);\n return;\n }\n\n let cancelled = false;\n\n setLoading(true);\n setError(\"\");\n\n const timeout = window.setTimeout(() => {\n if (cancelled) return;\n\n const fromIndex = BASE_INDEX[fromCurrency];\n const toIndex = BASE_INDEX[toCurrency];\n\n if (!fromIndex || !toIndex) {\n setError(\"Unsupported currency selection\");\n setLoading(false);\n return;\n }\n\n const nextRate = toIndex / fromIndex;\n\n setRate(nextRate);\n setResult(numericAmount * nextRate);\n setIsFlipped((previous) => !previous);\n setLoading(false);\n }, 220);\n\n return () => {\n cancelled = true;\n window.clearTimeout(timeout);\n };\n }, [amount, fromCurrency, toCurrency]);\n\n const formattedResult = useMemo(() => {\n if (result === null) return \"0.00\";\n\n return result.toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n });\n }, [result]);\n\n const activeRate = useMemo(() => {\n if (!rate) return null;\n\n return rate.toFixed(4);\n }, [rate]);\n\n const handleSwap = () => {\n setFromCurrency(toCurrency);\n setToCurrency(fromCurrency);\n };\n\n const amountSymbol =\n CURRENCIES.find((currency) => currency.code === fromCurrency)?.symbol ??\n \"$\";\n const resultSymbol =\n CURRENCIES.find((currency) => currency.code === toCurrency)?.symbol ?? \"$\";\n\n return (\n \n
\n
\n
\n

\n \n Currency Converter\n

\n

\n Simulated real-time exchange experience\n

\n
\n\n
\n
\n \n
\n
\n \n {amountSymbol}\n \n setAmount(event.target.value)}\n placeholder=\"Amount\"\n className=\"w-full rounded-lg border border-border bg-background/70 px-8 py-3 text-lg font-semibold text-foreground shadow-sm outline-none transition focus-visible:ring-2 focus-visible:ring-primary/40\"\n />\n
\n \n setFromCurrency(event.target.value as CurrencyCode)\n }\n className=\"w-[132px] rounded-lg border border-border bg-background px-3 py-3 text-sm font-semibold text-foreground shadow-sm transition focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n {CURRENCIES.map((currency) => (\n \n ))}\n \n
\n
\n\n
\n \n \n \n
\n\n
\n \n
\n \n \n {resultSymbol}\n \n \n \n \n setToCurrency(event.target.value as CurrencyCode)\n }\n className=\"w-[132px] rounded-lg border border-border bg-background px-3 py-3 text-sm font-semibold text-foreground shadow-sm transition focus-visible:ring-2 focus-visible:ring-primary/40\"\n >\n {CURRENCIES.map((currency) => (\n \n ))}\n \n
\n
\n\n {loading && (\n \n \n Calculating latest rates...\n \n )}\n\n {!loading && activeRate && (\n \n 1 {fromCurrency} ≈ {activeRate} {toCurrency}\n \n )}\n\n {error && (\n \n {error}\n \n )}\n\n {!loading && !error && (\n

\n Rates are approximated for demo purposes and refresh with each\n change.\n

\n )}\n
\n
\n \n );\n}\n", "type": "registry:component", "target": "components/uitripled/currency-converter-card-shadcnui.tsx" } ] }