synor/apps/desktop-wallet/src/components/Animations.tsx
Gulshan Yadav 81347ab15d feat(wallet): add comprehensive desktop wallet features
Add all-in-one desktop wallet with extensive feature set:

Infrastructure:
- Storage: IPFS-based decentralized file storage with upload/download
- Hosting: Domain registration and static site hosting
- Compute: GPU/CPU job marketplace for distributed computing
- Database: Multi-model database services (KV, Document, Vector, etc.)

Financial Features:
- Privacy: Confidential transactions with Pedersen commitments
- Bridge: Cross-chain transfers (Ethereum, Bitcoin, IBC/Cosmos)
- Governance: DAO proposals, voting, and delegation
- ZK-Rollup: L2 scaling with deposits, withdrawals, and transfers

UI/UX Improvements:
- Add ErrorBoundary component for graceful error handling
- Add LoadingStates components (spinners, skeletons, overlays)
- Add Animation components (FadeIn, SlideIn, CountUp, etc.)
- Update navigation with new feature sections

Testing:
- Add Playwright E2E smoke tests
- Test route accessibility and page rendering
- Verify build process and asset loading

Build:
- Fix TypeScript compilation errors
- Update Tauri plugin dependencies
- Successfully build macOS app bundle and DMG
2026-02-02 11:35:21 +05:30

270 lines
5 KiB
TypeScript

import { ReactNode, useEffect, useState } from 'react';
/**
* Fade in animation wrapper
*/
export function FadeIn({
children,
delay = 0,
duration = 300,
className = '',
}: {
children: ReactNode;
delay?: number;
duration?: number;
className?: string;
}) {
const [visible, setVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setVisible(true), delay);
return () => clearTimeout(timer);
}, [delay]);
return (
<div
className={className}
style={{
opacity: visible ? 1 : 0,
transition: `opacity ${duration}ms ease-in-out`,
}}
>
{children}
</div>
);
}
/**
* Slide in from direction
*/
export function SlideIn({
children,
direction = 'left',
delay = 0,
duration = 300,
distance = 20,
className = '',
}: {
children: ReactNode;
direction?: 'left' | 'right' | 'up' | 'down';
delay?: number;
duration?: number;
distance?: number;
className?: string;
}) {
const [visible, setVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setVisible(true), delay);
return () => clearTimeout(timer);
}, [delay]);
const transforms = {
left: `translateX(${visible ? 0 : -distance}px)`,
right: `translateX(${visible ? 0 : distance}px)`,
up: `translateY(${visible ? 0 : -distance}px)`,
down: `translateY(${visible ? 0 : distance}px)`,
};
return (
<div
className={className}
style={{
opacity: visible ? 1 : 0,
transform: transforms[direction],
transition: `all ${duration}ms ease-out`,
}}
>
{children}
</div>
);
}
/**
* Scale in animation
*/
export function ScaleIn({
children,
delay = 0,
duration = 200,
className = '',
}: {
children: ReactNode;
delay?: number;
duration?: number;
className?: string;
}) {
const [visible, setVisible] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setVisible(true), delay);
return () => clearTimeout(timer);
}, [delay]);
return (
<div
className={className}
style={{
opacity: visible ? 1 : 0,
transform: `scale(${visible ? 1 : 0.95})`,
transition: `all ${duration}ms ease-out`,
}}
>
{children}
</div>
);
}
/**
* Stagger children animations
*/
export function StaggerChildren({
children,
staggerDelay = 50,
initialDelay = 0,
className = '',
}: {
children: ReactNode[];
staggerDelay?: number;
initialDelay?: number;
className?: string;
}) {
return (
<div className={className}>
{children.map((child, index) => (
<FadeIn key={index} delay={initialDelay + index * staggerDelay}>
{child}
</FadeIn>
))}
</div>
);
}
/**
* Pulse animation (for attention)
*/
export function Pulse({
children,
className = '',
}: {
children: ReactNode;
className?: string;
}) {
return (
<div className={`animate-pulse ${className}`}>
{children}
</div>
);
}
/**
* Bounce animation
*/
export function Bounce({
children,
className = '',
}: {
children: ReactNode;
className?: string;
}) {
return (
<div className={`animate-bounce ${className}`}>
{children}
</div>
);
}
/**
* Number counter animation
*/
export function CountUp({
end,
start = 0,
duration = 1000,
decimals = 0,
prefix = '',
suffix = '',
className = '',
}: {
end: number;
start?: number;
duration?: number;
decimals?: number;
prefix?: string;
suffix?: string;
className?: string;
}) {
const [count, setCount] = useState(start);
useEffect(() => {
const startTime = Date.now();
const diff = end - start;
const animate = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Ease out cubic
const eased = 1 - Math.pow(1 - progress, 3);
const current = start + diff * eased;
setCount(current);
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}, [end, start, duration]);
return (
<span className={className}>
{prefix}
{count.toFixed(decimals)}
{suffix}
</span>
);
}
/**
* Typing animation for text
*/
export function TypeWriter({
text,
speed = 50,
delay = 0,
className = '',
onComplete,
}: {
text: string;
speed?: number;
delay?: number;
className?: string;
onComplete?: () => void;
}) {
const [displayed, setDisplayed] = useState('');
useEffect(() => {
let index = 0;
const timer = setTimeout(() => {
const interval = setInterval(() => {
setDisplayed(text.slice(0, index + 1));
index++;
if (index >= text.length) {
clearInterval(interval);
onComplete?.();
}
}, speed);
return () => clearInterval(interval);
}, delay);
return () => clearTimeout(timer);
}, [text, speed, delay, onComplete]);
return (
<span className={className}>
{displayed}
<span className="animate-pulse">|</span>
</span>
);
}