{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable-card-tailwind",
  "type": "registry:item",
  "title": "Expandable Card (Tailwind)",
  "description": "A card component that can expand to show more content.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/brook/tailwind/ui/expandable-card.tsx",
      "content": "\"use client\";\nimport { Dialog } from \"@base-ui/react/dialog\";\nimport { ScrollArea } from \"@base-ui/react/scroll-area\";\nimport { Plus, X } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ntype ExpandableCardItem = {\n  id: string | number;\n  imageSrc: string;\n  alt: string;\n  cardHeading: string;\n  content?: React.ReactNode;\n};\n\ntype ExpandableCardProps = {\n  item: ExpandableCardItem;\n  className?: string;\n};\n\nfunction ExpandableCard({ item, className }: ExpandableCardProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const [isElevated, setIsElevated] = useState(false);\n  const prefersReducedMotion = useReducedMotion();\n\n  return (\n    <div className={cn(\"relative h-fit w-fit\", className)} style={{ zIndex: isElevated ? 100 : undefined }}>\n      <Dialog.Root\n        onOpenChange={(open) => {\n          setIsOpen(open);\n          if (open) {\n            setIsElevated(true);\n          }\n        }}\n        open={isOpen}\n      >\n        <AnimatePresence>\n          {isOpen ? (\n            <Dialog.Backdrop\n              hidden={undefined}\n              key=\"overlay\"\n              render={\n                <motion.div\n                  animate={{\n                    opacity: 0.99,\n                  }}\n                  className=\"fixed inset-0 z-[101] min-h-dvh bg-background\"\n                  exit={{ opacity: 0 }}\n                  initial={{ opacity: 0 }}\n                  transition={{\n                    delay: prefersReducedMotion ? 0 : 0.1,\n                    duration: prefersReducedMotion ? 0 : 0.25,\n                    ease: [0.19, 1, 0.22, 1],\n                  }}\n                />\n              }\n            />\n          ) : null}\n        </AnimatePresence>\n        <Dialog.Portal keepMounted>\n          <AnimatePresence>\n            {isOpen ? (\n              <div\n                className=\"pointer-events-none fixed inset-0 z-[9999] flex max-h-full items-center justify-center overflow-y-auto\"\n                key=\"positioner\"\n              >\n                <Dialog.Popup\n                  hidden={undefined}\n                  render={\n                    <motion.div\n                      className={cn(\n                        \"fixed top-[5vh] h-dvh w-full max-w-[960px] overflow-hidden\",\n                        \"bg-[var(--mix-card-15-bg)] p-0 shadow-[inset_0_0_0_0.5px_oklch(from_var(--border)_l_c_h/0.6)]\",\n                        \"pointer-events-auto flex flex-col items-center gap-[16px]\",\n                        \"transform-none animate-none opacity-100 transition-none\"\n                      )}\n                      layoutId={`card-${item.id}`}\n                      style={{\n                        borderRadius: \"32px\",\n                        overflow: \"hidden\",\n                      }}\n                    />\n                  }\n                >\n                  <ScrollArea.Root className=\"relative h-full w-full\">\n                    <ScrollArea.Viewport\n                      className={cn(\n                        \"h-full w-full overscroll-contain\",\n                        \"before:[--scroll-area-overflow-y-start:inherit] after:[--scroll-area-overflow-y-end:inherit]\",\n                        \"before:content-[''] after:content-['']\",\n                        \"before:block after:block\",\n                        \"before:absolute after:absolute\",\n                        \"before:left-0 after:left-0\",\n                        \"before:w-full after:w-full\",\n                        \"before:pointer-events-none after:pointer-events-none\",\n                        \"before:rounded-md after:rounded-md\",\n                        \"before:transition-[height] after:transition-[height]\",\n                        \"before:duration-100 after:duration-100\",\n                        \"before:ease-out after:ease-out\",\n                        \"before:z-10 after:z-10\",\n                        \"before:top-0 after:bottom-0\",\n                        \"before:bg-[linear-gradient(to_bottom,oklch(from_var(--background)_l_c_h_/_0.7),transparent)]\",\n                        \"after:bg-[linear-gradient(to_top,var(--background)_0%,var(--background)_30%,transparent_100%)]\",\n                        \"before:[height:min(40px,var(--scroll-area-overflow-y-start))]\",\n                        \"after:[height:min(10vh,var(--scroll-area-overflow-y-end,10vh))]\"\n                      )}\n                    >\n                      <ScrollArea.Content\n                        className={cn(\n                          \"flex w-full flex-col items-center gap-[16px]\",\n                          \"px-[max(1.5rem,env(safe-area-inset-left))] pt-0 pb-[max(12vh,env(safe-area-inset-bottom))]\"\n                        )}\n                      >\n                        <div className=\"sticky top-8 z-20 mr-2 flex h-11 w-11 cursor-pointer items-center justify-center self-end rounded-full\">\n                          <Dialog.Close\n                            aria-label=\"Close\"\n                            className={cn(\n                              \"z-20 rounded-full p-2 shadow-[inset_0_0_0_0.5px_oklch(from_var(--border)_l_c_h_/_0.7)]\",\n                              \"flex cursor-pointer items-center justify-center bg-[var(--background)] text-[var(--muted-foreground)] transition-colors duration-150 ease-out md:bg-transparent\",\n                              \"hover:bg-[var(--muted)] hover:text-[var(--foreground)]\",\n                              \"focus-visible:outline-2 focus-visible:outline-[var(--ring)] focus-visible:outline-offset-2\",\n                              \"motion-reduce:transition-none\"\n                            )}\n                            render={\n                              <motion.button\n                                animate={{ opacity: 1 }}\n                                exit={{ opacity: 0, display: \"flex\" }}\n                                initial={{ opacity: prefersReducedMotion ? 1 : 0 }}\n                                transition={{\n                                  type: \"spring\",\n                                  duration: prefersReducedMotion ? 0 : 0.3,\n                                  delay: prefersReducedMotion ? 0 : 0.1,\n                                }}\n                              />\n                            }\n                          >\n                            <X height={21} strokeWidth={2} width={21} />\n                          </Dialog.Close>\n                        </div>\n\n                        <motion.img\n                          alt={item.alt}\n                          className=\"h-auto w-full max-w-[700px] object-contain\"\n                          height={600}\n                          layoutId={`image-${item.id}`}\n                          src={item.imageSrc}\n                          style={{ borderRadius: \"24px\" }}\n                          width={600}\n                        />\n\n                        <motion.div className=\"mx-auto flex h-auto w-full max-w-[700px] flex-col items-start gap-9 pt-7 pr-0 pb-0 pl-0 text-left leading-[2]\">\n                          <motion.div layoutId={`heading-${item.id}`}>\n                            <h3 className=\"m-0 w-full self-start font-medium text-[48px] text-[var(--foreground)] leading-[1.5] tracking-[-0.02em]\">\n                              {item.cardHeading}\n                            </h3>\n                          </motion.div>\n\n                          <motion.div\n                            animate={{ opacity: 1, y: 0, scale: 1 }}\n                            className=\"text-[oklch(from_var(--secondary-foreground)_l_c_h_/_0.8)]\"\n                            exit={{\n                              opacity: 0,\n                              display: \"block\",\n                              y: prefersReducedMotion ? 0 : -40,\n                              scale: prefersReducedMotion ? 1 : 0.92,\n                            }}\n                            initial={{\n                              opacity: prefersReducedMotion ? 1 : 0,\n                              y: prefersReducedMotion ? 0 : -40,\n                              scale: prefersReducedMotion ? 1 : 0.92,\n                            }}\n                            transition={{\n                              delay: prefersReducedMotion ? 0 : 0.1,\n                              duration: prefersReducedMotion ? 0 : 0.3,\n                              type: \"spring\",\n                              bounce: 0,\n                            }}\n                          >\n                            {item.content}\n                          </motion.div>\n                        </motion.div>\n                      </ScrollArea.Content>\n                    </ScrollArea.Viewport>\n                    <ScrollArea.Scrollbar\n                      className={cn(\n                        \"flex w-1.5 justify-center rounded-sm\",\n                        \"m-[2px] mt-[calc(32px+0.5vh)]\",\n                        \"touch-none select-none\",\n                        \"pointer-events-none opacity-0\",\n                        \"transition-opacity delay-[800ms] duration-150 ease-out\",\n                        \"data-[scrolling]:pointer-events-auto data-[scrolling]:opacity-100 data-[scrolling]:delay-0\"\n                      )}\n                      orientation=\"vertical\"\n                    >\n                      <ScrollArea.Thumb\n                        className={cn(\n                          \"w-full rounded\",\n                          \"bg-[oklch(from_var(--border)_l_c_h_/_0.5)]\",\n                          \"transition-colors duration-150 ease-out\",\n                          \"hover:bg-[oklch(from_var(--border)_l_c_h_/_0.8)]\"\n                        )}\n                      />\n                    </ScrollArea.Scrollbar>\n                  </ScrollArea.Root>\n                </Dialog.Popup>\n              </div>\n            ) : null}\n          </AnimatePresence>\n        </Dialog.Portal>\n\n        <Dialog.Trigger\n          render={\n            <motion.button\n              aria-label={`Expand ${item.cardHeading}`}\n              className=\"group flex w-[320px] cursor-pointer flex-col items-center overflow-hidden bg-transparent p-0 shadow-[inset_0_0_0_0.5px_oklch(from_var(--border)_l_c_h_/_0.7)] focus-visible:outline-2 focus-visible:outline-[var(--ring)] focus-visible:outline-offset-2\"\n              layoutId={`card-${item.id}`}\n              onLayoutAnimationComplete={() => {\n                if (!isOpen) {\n                  setIsElevated(false);\n                }\n              }}\n              style={{\n                all: \"unset\",\n                cursor: \"pointer\",\n                display: \"flex\",\n                flexDirection: \"column\",\n                alignItems: \"center\",\n                width: \"320px\",\n                overflow: \"hidden\",\n                borderRadius: \"24px\",\n                position: \"relative\",\n              }}\n            />\n          }\n        >\n          <div className=\"pointer-events-none absolute inset-0 rounded-3xl shadow-[inset_0_0_0_0.5px_oklch(from_var(--border)_l_c_h_/_0.7)]\" />\n          <motion.img\n            alt={item.alt}\n            className=\"h-[320px] w-full object-cover\"\n            height={300}\n            layoutId={`image-${item.id}`}\n            src={item.imageSrc}\n            style={{ borderRadius: \"24px\" }}\n            width={300}\n          />\n\n          <div className=\"flex w-full items-center justify-center p-4\">\n            <motion.div layoutId={`heading-${item.id}`}>\n              <h3 className=\"m-0 overflow-hidden text-ellipsis whitespace-nowrap font-medium text-2xl text-[var(--secondary-foreground)] leading-[1.5] tracking-[-0.02em] transition-colors duration-150 ease-out group-hover:text-[var(--foreground)] motion-reduce:transition-none\">\n                {item.cardHeading}\n              </h3>\n            </motion.div>\n\n            <motion.div className=\"ml-auto flex h-9 min-h-9 w-9 min-w-9 shrink-0 items-center justify-center rounded-full text-[var(--muted-foreground)] shadow-[inset_0_0_0_0.5px_oklch(from_var(--border)_l_c_h_/_0.7)] transition-colors duration-150 ease-out hover:bg-[var(--card)] hover:text-[var(--foreground)] group-hover:bg-[var(--card)] group-hover:text-[var(--foreground)] motion-reduce:transition-none\">\n              <Plus height={21} strokeWidth={2} width={21} />\n            </motion.div>\n          </div>\n        </Dialog.Trigger>\n      </Dialog.Root>\n    </div>\n  );\n}\n\nexport { ExpandableCard };\nexport type { ExpandableCardItem, ExpandableCardProps };\n",
      "type": "registry:file",
      "target": "~/components/ui/expandable-card/expandable-card.tsx"
    }
  ]
}