TypeScript/React ```typescript
// Bottom Navigation
interface NavItem {
href: string;
label: string;
icon: string;
}
function MobileNav({ items }: { items: NavItem[] }) {
const pathname = usePathname();
return (
{items.map((item) => {
const isActive = pathname.startsWith(item.href);
return (
key={item.href}
href={item.href}
className={`flex flex-col items-center gap-1 px-4 py-2 min-w-[64px] ${
isActive ? 'text-primary-400' : 'text-neutral-500'
}`}
>
{item.icon}
{item.label}
);
})}
);
}
// Bottom Sheet
interface BottomSheetProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
title?: string;
}
function BottomSheet({ isOpen, onClose, children, title }: BottomSheetProps) {
const [dragY, setDragY] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const startY = useRef(0);
useEffect(() => {
document.body.style.overflow = isOpen ? 'hidden' : '';
return () => { document.body.style.overflow = ''; };
}, [isOpen]);
const handleTouchStart = (e: React.TouchEvent) => {
startY.current = e.touches[0].clientY;
setIsDragging(true);
};
const handleTouchMove = (e: React.TouchEvent) => {
if (!isDragging) return;
const diff = e.touches[0].clientY - startY.current;
if (diff > 0) setDragY(diff);
};
const handleTouchEnd = () => {
setIsDragging(false);
if (dragY > 100) onClose();
setDragY(0);
};
if (!isOpen) return null;
return (
<>
className="fixed bottom-0 left-0 right-0 bg-neutral-800 rounded-t-2xl z-50 max-h-[90vh]"
style={{
transform: translateY(${dragY}px),
transition: isDragging ? 'none' : 'transform 0.3s ease-out',
}}
>
className="flex justify-center py-3 cursor-grab touch-none"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{title && (
)}
{children}
>
);
}
// Pull to Refresh
function PullToRefresh({
onRefresh,
children
}: {
onRefresh: () => Promise;
children: ReactNode;
}) {
const [isPulling, setIsPulling] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [pullDistance, setPullDistance] = useState(0);
const startY = useRef(0);
const containerRef = useRef(null);
const THRESHOLD = 80;
const handleTouchStart = (e: React.TouchEvent) => {
if (containerRef.current?.scrollTop === 0) {
startY.current = e.touches[0].clientY;
setIsPulling(true);
}
};
const handleTouchMove = (e: React.TouchEvent) => {
if (!isPulling || isRefreshing) return;
const diff = e.touches[0].clientY - startY.current;
if (diff > 0) {
setPullDistance(Math.min(diff 0.5, THRESHOLD 1.5));
}
};
const handleTouchEnd = async () => {
if (!isPulling) return;
if (pullDistance >= THRESHOLD && !isRefreshing) {
setIsRefreshing(true);
setPullDistance(THRESHOLD);
try { await onRefresh(); }
finally { setIsRefreshing(false); }
}
setIsPulling(false);
setPullDistance(0);
};
return (
ref={containerRef}
className="h-full overflow-y-auto"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
className="flex items-center justify-center overflow-hidden"
style={{ height: pullDistance }}
>
{isRefreshing ? (
) : (
rotate(${(pullDistance / THRESHOLD) * 180}deg) }}>β
)}
{children}
);
}
// Swipeable Row
function SwipeableRow({
children,
onSwipeLeft,
onSwipeRight,
leftAction,
rightAction,
}: {
children: ReactNode;
onSwipeLeft?: () => void;
onSwipeRight?: () => void;
leftAction?: ReactNode;
rightAction?: ReactNode;
}) {
const [translateX, setTranslateX] = useState(0);
const startX = useRef(0);
const isDragging = useRef(false);
const THRESHOLD = 80;
const handleTouchStart = (e: React.TouchEvent) => {
startX.current = e.touches[0].clientX;
isDragging.current = true;
};
const handleTouchMove = (e: React.TouchEvent) => {
if (!isDragging.current) return;
const diff = e.touches[0].clientX - startX.current;
setTranslateX(Math.max(-100, Math.min(100, diff)));
};
const handleTouchEnd = () => {
isDragging.current = false;
if (translateX > THRESHOLD) onSwipeRight?.();
else if (translateX < -THRESHOLD) onSwipeLeft?.();
setTranslateX(0);
};
return (
{leftAction && (
)}
{rightAction && (
)}
className="relative bg-neutral-800"
style={{
transform: translateX(${translateX}px),
transition: isDragging.current ? 'none' : 'transform 0.2s ease-out',
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{children}
);
}
```