diff --git a/components/converter/converter-card.tsx b/components/converter/converter-card.tsx index 39ac2e1..c7b4c4e 100644 --- a/components/converter/converter-card.tsx +++ b/components/converter/converter-card.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { AnimatePresence, motion, useReducedMotion } from "framer-motion"; import { AlertTriangle, ArrowUpDown, @@ -140,6 +141,7 @@ export function ConverterCard({ onPairChange, multiConversionCodes, }: ConverterCardProps) { + const shouldReduceMotion = useReducedMotion(); const { data, error, isLoading, refresh } = useMarketRates(); const [amountInput, setAmountInput] = useState("1"); @@ -147,6 +149,7 @@ export function ConverterCard({ const [toCode, setToCode] = useState(DEFAULT_TO); const [isCopied, setIsCopied] = useState(false); const [marketRange, setMarketRange] = useState("24h"); + const [swapAnimationStep, setSwapAnimationStep] = useState(0); const debouncedAmount = useDebouncedValue(amountInput, 120); @@ -250,6 +253,7 @@ export function ConverterCard({ }, [fromAsset, toAsset]); const handleSwap = () => { + setSwapAnimationStep((current) => current + 1); setFromCode(toCode); setToCode(fromCode); }; @@ -364,6 +368,8 @@ export function ConverterCard({ : "--"; const amountError = inputValidation.ok ? null : inputValidation.error; + const pairTransitionKey = + fromAsset && toAsset ? `${fromAsset.code}-${toAsset.code}` : "empty"; return ( @@ -505,7 +511,26 @@ export function ConverterCard({ onClick={handleSwap} aria-label="Swap currencies" > - + + + {fromAsset && toAsset ? ( -
- - - {fromAsset.code} - - to - - - {toAsset.code} - -
+ + + + + {fromAsset.code} + + to + + + {toAsset.code} + + + ) : null}

- {convertedDisplay} + + + {convertedDisplay} + +

{fromAsset ? (

@@ -609,7 +678,30 @@ export function ConverterCard({ {asset.code}

- {formatAmount(value, asset)} {asset.code} + + + {formatAmount(value, asset)} {asset.code} + +

))} diff --git a/components/converter/currency-select.tsx b/components/converter/currency-select.tsx index 08c60b0..af79a1c 100644 --- a/components/converter/currency-select.tsx +++ b/components/converter/currency-select.tsx @@ -1,6 +1,7 @@ "use client"; import { useMemo, useState } from "react"; +import { AnimatePresence, motion, useReducedMotion } from "framer-motion"; import { Check, ChevronDown } from "lucide-react"; import { POPULAR_CODES } from "@/lib/assets"; @@ -61,6 +62,7 @@ export function CurrencySelect({ disabled }: CurrencySelectProps) { const [open, setOpen] = useState(false); + const shouldReduceMotion = useReducedMotion(); const selectedAsset = useMemo( () => assets.find((asset) => asset.code === value), @@ -103,7 +105,32 @@ export function CurrencySelect({ disabled={disabled} className="h-auto w-full justify-between rounded-xl border-border/70 px-3 py-2.5" > - + + + + + + + diff --git a/package-lock.json b/package-lock.json index a803333..5c4ffac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cmdk": "^1.0.4", "cryptocurrency-icons": "^0.18.1", "currency-flags": "github:vivekimsit/currency-flags", + "framer-motion": "^12.36.0", "lucide-react": "^0.475.0", "next": "^14.2.24", "react": "^18.2.0", @@ -3502,6 +3503,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.36.0.tgz", + "integrity": "sha512-4PqYHAT7gev0ke0wos+PyrcFxI0HScjm3asgU8nSYa8YzJFuwgIvdj3/s3ZaxLq0bUSboIn19A2WS/MHwLCvfw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.36.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4688,6 +4716,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.36.0.tgz", + "integrity": "sha512-Ep1pq8P88rGJ75om8lTCA13zqd7ywPGwCqwuWwin6BKc0hMLkVfcS6qKlRqEo2+t0DwoUcgGJfXwaiFn4AOcQA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 1e795e2..8c1a0ee 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "cmdk": "^1.0.4", "cryptocurrency-icons": "^0.18.1", "currency-flags": "github:vivekimsit/currency-flags", + "framer-motion": "^12.36.0", "lucide-react": "^0.475.0", "next": "^14.2.24", "react": "^18.2.0",