Sunflower UI
Search
Copy Code
Light Mode
Scenic view
aspect-ratio-demo.tsx
prompt.md
Collapse
Reset
Copy Code
Download
Run
1
"use client"
2
3
import * as React from "react"
4
import { motion, AnimatePresence } from "framer-motion"
5
import { cn } from "@/lib/utils"
6
7
const RATIOS = [
8
{ name: "16 / 9", value: 16 / 9, iconClassName: "w-[18px] h-[10px]" },
9
{ name: "3 / 2", value: 3 / 2, iconClassName: "w-[16px] h-[10px]" },
10
{ name: "4 / 3", value: 4 / 3, iconClassName: "w-[14px] h-[10.5px]" },
11
{ name: "1 / 1", value: 1 / 1, iconClassName: "w-[12px] h-[12px]" },
12
{ name: "3 / 4", value: 3 / 4, iconClassName: "w-[10.5px] h-[14px]" },
13
{ name: "9 / 16", value: 9 / 16, iconClassName: "w-[10px] h-[18px]" },
14
]
15
16
export default function AspectRatioDemo() {
17
const [activeRatio, setActiveRatio] = React.useState(RATIOS[0])
18
const [hoveredRatio, setHoveredRatio] = React.useState<string | null>(null)
19
20
return (
21
<div className="w-full h-full min-h-[600px] flex flex-col items-center justify-center p-10 bg-white dark:bg-[#1C1C1C]">
22
23
{/* Image Container */}
24
<div className="w-full max-w-[600px] h-[400px] flex items-center justify-center mb-12">
25
<motion.div
26
layout
27
initial={false}
28
animate={{ aspectRatio: activeRatio.value }}
29
transition={{ type: "spring", stiffness: 400, damping: 30 }}
30
className="relative w-full max-w-full max-h-full rounded-2xl overflow-hidden shadow-2xl dark:shadow-black/50"
31
>
32
<motion.img
33
layout
34
src="/carousel/1.png"
35
alt="Scenic view"
36
className="absolute inset-0 w-full h-full object-cover"
37
/>
38
</motion.div>
39
</div>
40
41
{/* Toolbar Dock */}
42
<div className="relative flex items-center p-2 rounded-full bg-black/5 dark:bg-white/[0.04] border border-black/10 dark:border-white/[0.05] shadow-sm backdrop-blur-md gap-1">
43
{RATIOS.map((ratio) => {
44
const isActive = activeRatio.name === ratio.name
45
const isHovered = hoveredRatio === ratio.name
46
47
return (
48
<div
49
key={ratio.name}
50
className="relative"
51
onMouseEnter={() => setHoveredRatio(ratio.name)}
52
onMouseLeave={() => setHoveredRatio(null)}
53
>
54
{/* Tooltip */}
55
<AnimatePresence>
56
{isHovered && (
57
<motion.div
58
initial={{ opacity: 0, y: 10, scale: 0.9 }}
59
animate={{ opacity: 1, y: 0, scale: 1 }}
60
exit={{ opacity: 0, y: 10, scale: 0.9 }}
61
transition={{ duration: 0.2 }}
62
className="absolute -top-12 left-1/2 -translate-x-1/2 px-3 py-1.5 rounded-lg bg-zinc-900 dark:bg-zinc-800 text-white text-xs font-medium whitespace-nowrap shadow-xl pointer-events-none"
63
>
64
{ratio.name}
65
</motion.div>
66
)}
67
</AnimatePresence>
68
69
<button
70
onClick={() => setActiveRatio(ratio)}
71
className={cn(
72
"relative flex items-center justify-center w-12 h-10 rounded-full transition-colors outline-none",
73
isActive ? "bg-black/10 dark:bg-black/40 text-black dark:text-white" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-300"
74
)}
75
>
76
{isActive && (
77
<motion.div
78
layoutId="active-ratio-bg"
79
className="absolute inset-0 rounded-full bg-black/5 dark:bg-black/60 shadow-inner"
80
transition={{ type: "spring", stiffness: 400, damping: 30 }}
81
/>
82
)}
83
84
<div
85
className={cn(
86
"relative border-[1.5px] rounded-[4px] transition-colors duration-200",
87
ratio.iconClassName,
88
isActive ? "border-zinc-900 dark:border-white" : "border-zinc-400 dark:border-zinc-500"
89
)}
90
/>
91
</button>
92
</div>
93
)
94
})}
95
</div>
96
</div>
97
)
98
}
99