{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-layout",
  "title": "Animated Layout",
  "description": "A modern and dynamic layout component featuring fluid animations, smooth transitions, and a responsive grid system.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/radix-nova/animated-layout/animated-layout.tsx",
      "content": "\"use client\";\r\n\r\nimport React, { useState } from \"react\";\r\nimport { motion, LayoutGroup, AnimatePresence, type Transition } from \"motion/react\";\r\nimport { List, LayoutGrid, Layers, Star, Check } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\nexport interface CollectionItem {\r\n    id: string;\r\n    title: string;\r\n    subtitle: string;\r\n    badge: string;\r\n    image: string;\r\n    icon: React.ElementType;\r\n}\r\n\r\nexport type ViewMode = \"list\" | \"card\" | \"pack\";\r\n\r\n// ─── Constants (hoisted – zero per-render cost) ──────────────────────────────\r\n\r\nconst S: Transition = { type: \"spring\", stiffness: 400, damping: 32, mass: 0.8 };\r\nconst S_TAB: Transition = { type: \"spring\", stiffness: 500, damping: 35, mass: 0.6 };\r\nconst S_CHECK: Transition = { type: \"spring\", stiffness: 500, damping: 30 };\r\nconst FADE: Transition = { duration: 0.12, ease: [0.25, 0.1, 0.25, 1] };\r\n\r\nconst GPU: React.CSSProperties = { willChange: \"transform\", transform: \"translateZ(0)\", backfaceVisibility: \"hidden\" };\r\nconst RESET = { rotate: 0, x: 0, y: 0 };\r\nconst PACK_X = 22;\r\n\r\nconst TABS: { mode: ViewMode; icon: React.ElementType }[] = [\r\n    { mode: \"list\", icon: List },\r\n    { mode: \"card\", icon: LayoutGrid },\r\n    { mode: \"pack\", icon: Layers },\r\n];\r\n\r\n// ─── View-mode class maps (avoids repeated ternaries in render) ──────────────\r\n\r\nconst GRID_CLS: Record<ViewMode, string> = {\r\n    list: \"flex flex-col gap-2\",\r\n    card: \"grid grid-cols-2 gap-3\",\r\n    pack: \"h-64 flex items-center justify-center mt-4\",\r\n};\r\n\r\nconst CARD_CLS: Record<ViewMode, string> = {\r\n    list: \"flex-row gap-4 w-full rounded-lg\",\r\n    card: \"flex-col gap-3 w-full items-start rounded-xl\",\r\n    pack: \"absolute w-56 h-56 items-center justify-center rounded-xl\",\r\n};\r\n\r\nconst IMG_CLS: Record<ViewMode, string> = {\r\n    list: \"w-16 h-16 rounded-lg border border-border\",\r\n    card: \"w-full aspect-square rounded-lg border border-border shadow-sm\",\r\n    pack: \"w-full h-full rounded-lg border border-border shadow-lg\",\r\n};\r\n\r\nconst WRAP_CLS: Record<ViewMode, string> = {\r\n    list: \"p-2\",\r\n    card: \"p-3\",\r\n    pack: \"py-4\",\r\n};\r\n\r\n// ─── Sub-components ──────────────────────────────────────────────────────────\r\n\r\nconst ViewTab = React.memo(function ViewTab({ active, onClick, icon: Icon }: {\r\n    active: boolean; onClick: () => void; icon: React.ElementType;\r\n}) {\r\n    return (\r\n        <button\r\n            type=\"button\"\r\n            onClick={onClick}\r\n            className={cn(\r\n                \"relative flex items-center px-2.5 py-1.5 rounded-md outline-none transition-colors duration-150\",\r\n                active ? \"text-primary-foreground\" : \"text-muted-foreground hover:text-foreground\"\r\n            )}\r\n        >\r\n            {active && (\r\n                <motion.span\r\n                    layoutId=\"animated-layout-active-tab\"\r\n                    className=\"absolute inset-0 rounded-md bg-primary shadow-sm\"\r\n                    transition={S_TAB}\r\n                    style={GPU}\r\n                />\r\n            )}\r\n            <Icon size={14} strokeWidth={2} className={cn(\"relative z-10 transition-transform duration-200\", active && \"scale-110\")} />\r\n        </button>\r\n    );\r\n});\r\n\r\nconst ItemInfo = React.memo(function ItemInfo({ item, view }: { item: CollectionItem; view: ViewMode }) {\r\n    const Icon = item.icon;\r\n    const [checked, setChecked] = useState(false);\r\n    const isCard = view === \"card\";\r\n\r\n    return (\r\n        <motion.div\r\n            layout\r\n            initial={{ opacity: 0, scale: 0.92, filter: \"blur(4px)\" }}\r\n            animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }}\r\n            exit={{ opacity: 0, scale: 0.92, filter: \"blur(4px)\" }}\r\n            transition={FADE}\r\n            className={cn(\"flex flex-1 items-center justify-between min-w-0\", isCard ? \"w-full px-1 pb-1\" : \"px-0\")}\r\n        >\r\n            <div className=\"flex flex-col gap-0.5 min-w-0\">\r\n                <div className=\"flex items-center justify-start\">\r\n                    <h3 className={cn(\r\n                        \"font-medium text-[15px] leading-tight truncate transition-colors duration-200\",\r\n                        checked ? \"text-muted-foreground line-through decoration-muted-foreground/50\" : \"text-foreground\"\r\n                    )}>\r\n                        {item.title}\r\n                    </h3>\r\n                    <span className=\"flex items-center gap-1 px-1 rounded-sm bg-muted/50 border border-border text-primary text-[10px] font-bold shrink-0 ml-2\">\r\n                        <Star size={10} className=\"text-yellow-500 fill-yellow-500\" />\r\n                        {item.badge}\r\n                    </span>\r\n                </div>\r\n                <span className={cn(\r\n                    \"text-xs font-medium text-muted-foreground flex items-center gap-1.5 transition-opacity duration-200\",\r\n                    checked && \"opacity-50\"\r\n                )}>\r\n                    <span className=\"flex items-center justify-center p-[3px] bg-muted/50 border border-border rounded-sm\">\r\n                        <Icon size={12} strokeWidth={2} className=\"text-primary/70 shrink-0\" />\r\n                    </span>\r\n                    <span className=\"truncate\">{item.subtitle}</span>\r\n                </span>\r\n            </div>\r\n\r\n            <motion.button\r\n                type=\"button\"\r\n                role=\"checkbox\"\r\n                aria-checked={checked}\r\n                onClick={() => setChecked((p) => !p)}\r\n                className={cn(\r\n                    \"shrink-0 flex items-center justify-center border transition-all duration-200 outline-none rounded-sm\",\r\n                    isCard ? \"size-6\" : \"size-7 mr-2\",\r\n                    checked ? \"bg-card border-primary text-primary\" : \"bg-card border-border text-foreground/80 hover:border-primary/40\"\r\n                )}\r\n            >\r\n                <AnimatePresence mode=\"wait\">\r\n                    {checked && (\r\n                        <motion.span\r\n                            key=\"check\"\r\n                            initial={{ scale: 0, opacity: 0 }}\r\n                            animate={{ scale: 1, opacity: 1 }}\r\n                            exit={{ scale: 0, opacity: 0 }}\r\n                            transition={S_CHECK}\r\n                        >\r\n                            <Check size={isCard ? 14 : 16} strokeWidth={2.5} />\r\n                        </motion.span>\r\n                    )}\r\n                </AnimatePresence>\r\n            </motion.button>\r\n        </motion.div>\r\n    );\r\n});\r\n\r\n// ─── Main component ──────────────────────────────────────────────────────────\r\n\r\nexport interface AnimatedLayoutProps {\r\n    items: CollectionItem[];\r\n    defaultView?: ViewMode;\r\n    heading?: string;\r\n    className?: string;\r\n}\r\n\r\nexport function AnimatedLayout({ items, defaultView = \"list\", heading = \"My Tasks\", className }: AnimatedLayoutProps) {\r\n    const [view, setView] = useState<ViewMode>(defaultView);\r\n    const len = items.length;\r\n\r\n    return (\r\n        <div className={cn(\"w-full max-w-xl mx-auto p-4 md:p-8 font-sans select-none\", className)}>\r\n            <div className=\"w-full flex flex-col items-end gap-4\">\r\n                <div className=\"flex items-center justify-between w-full gap-5\">\r\n                    <h2 className=\"text-xl font-medium text-foreground\">{heading}</h2>\r\n                    <nav className=\"flex p-1 bg-muted rounded-lg w-fit border border-border\">\r\n                        {TABS.map((t) => (\r\n                            <ViewTab key={t.mode} active={view === t.mode} onClick={() => setView(t.mode)} icon={t.icon} />\r\n                        ))}\r\n                    </nav>\r\n                </div>\r\n\r\n                <div className={cn(\"relative w-full flex flex-col items-center border border-border rounded-xl bg-muted/60\", WRAP_CLS[view])}>\r\n                    <LayoutGroup>\r\n                        <motion.div layout transition={S} style={GPU} className={cn(\"w-full relative\", GRID_CLS[view])}>\r\n                            {items.map((item, i) => (\r\n                                <motion.div\r\n                                    key={item.id}\r\n                                    layout\r\n                                    transition={S}\r\n                                    style={{ ...GPU, zIndex: view === \"pack\" ? len - i : 1 }}\r\n                                    animate={view === \"pack\" ? { rotate: 0, x: (i - (len - 1) / 2) * PACK_X } : RESET}\r\n                                    className={cn(\"relative flex items-center bg-background p-1.5\", CARD_CLS[view])}\r\n                                >\r\n                                    <motion.div layout transition={S} className={cn(\"relative overflow-hidden shrink-0 bg-background\", IMG_CLS[view])}>\r\n                                        <motion.img layout transition={S} src={item.image} alt={item.title} loading=\"lazy\" decoding=\"async\" className=\"w-full h-full object-cover block\" />\r\n                                    </motion.div>\r\n                                    <AnimatePresence mode=\"popLayout\" initial={false}>\r\n                                        {view !== \"pack\" && <ItemInfo key={`${item.id}-info`} item={item} view={view} />}\r\n                                    </AnimatePresence>\r\n                                </motion.div>\r\n                            ))}\r\n                        </motion.div>\r\n                    </LayoutGroup>\r\n                </div>\r\n            </div>\r\n        </div>\r\n    );\r\n}\r\n\r\nexport default AnimatedLayout;",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}