{"version":1,"id":"accordion-list","framework":"next","kind":"block","files":[{"path":"src/blocks/gfa/accordion-list.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronDown } from \"lucide-react\";\nimport { motion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst panelSpring = {\n  type: \"spring\" as const,\n  stiffness: 360,\n  damping: 17,\n  mass: 0.62,\n};\n\nconst chevronSpring = {\n  type: \"spring\" as const,\n  stiffness: 500,\n  damping: 22,\n  mass: 0.35,\n};\n\nexport type AccordionListItem = {\n  id: string;\n  title: string;\n  content: React.ReactNode;\n};\n\nexport type AccordionListProps = {\n  items: AccordionListItem[];\n  /** `single`: FAQ-style — opening one closes another. */\n  type?: \"single\" | \"multiple\";\n  defaultOpenIds?: string[];\n  className?: string;\n};\n\nexport function AccordionList({ items, type = \"single\", defaultOpenIds, className }: AccordionListProps) {\n  const initialSet = React.useMemo(() => new Set(defaultOpenIds ?? []), [defaultOpenIds]);\n  const [open, setOpen] = React.useState<Set<string>>(() => new Set(initialSet));\n\n  const toggle = React.useCallback(\n    (id: string) => {\n      setOpen((prev) => {\n        const next = new Set(prev);\n        const isOn = next.has(id);\n        if (type === \"single\") {\n          next.clear();\n          if (!isOn) next.add(id);\n        } else {\n          if (isOn) next.delete(id);\n          else next.add(id);\n        }\n        return next;\n      });\n    },\n    [type],\n  );\n\n  return (\n    <div className={cn(\"flex flex-col gap-2\", className)} role=\"presentation\">\n      {items.map((item) => (\n        <AccordionRow key={item.id} item={item} isOpen={open.has(item.id)} onToggle={() => toggle(item.id)} />\n      ))}\n    </div>\n  );\n}\n\ntype AccordionRowProps = {\n  item: AccordionListItem;\n  isOpen: boolean;\n  onToggle: () => void;\n};\n\nfunction AccordionRow({ item, isOpen, onToggle }: AccordionRowProps) {\n  const panelId = React.useId();\n  const buttonId = React.useId();\n\n  return (\n    <div\n      className={cn(\"overflow-hidden rounded-xl border border-border/60 bg-card shadow-sm\")}\n    >\n      <h3 className=\"m-0\">\n        <button\n          id={buttonId}\n          type=\"button\"\n          className={cn(\n            \"flex w-full items-center justify-between gap-3 px-4 py-3.5 text-left text-sm font-semibold text-foreground\",\n            \"rounded-t-xl rounded-b-xl outline-none transition-colors hover:bg-muted/35 focus-visible:bg-muted/45\",\n            \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n            isOpen && \"rounded-b-none hover:bg-muted/25\",\n          )}\n          aria-expanded={isOpen}\n          aria-controls={panelId}\n          onClick={onToggle}\n        >\n          <span className=\"min-w-0 flex-1\">{item.title}</span>\n          <motion.span\n            aria-hidden\n            className=\"inline-flex size-8 shrink-0 items-center justify-center rounded-md text-muted-foreground\"\n            animate={{ rotate: isOpen ? 180 : 0 }}\n            transition={chevronSpring}\n          >\n            <ChevronDown className=\"size-4\" strokeWidth={2.25} />\n          </motion.span>\n        </button>\n      </h3>\n\n      <motion.div\n        id={panelId}\n        role=\"region\"\n        aria-labelledby={buttonId}\n        className=\"grid overflow-hidden\"\n        initial={false}\n        animate={{ gridTemplateRows: isOpen ? \"1fr\" : \"0fr\" }}\n        transition={panelSpring}\n      >\n        <div className=\"min-h-0\">\n          <motion.div\n            initial={false}\n            inert={!isOpen}\n            aria-hidden={!isOpen}\n            animate={{\n              opacity: isOpen ? 1 : 0,\n              y: isOpen ? 0 : -4,\n            }}\n            transition={{\n              opacity: { duration: 0.2 },\n              y: { type: \"spring\", stiffness: 480, damping: 26, mass: 0.42 },\n            }}\n            className={cn(\n              \"border-t border-border/50 bg-muted/5 px-4 pb-4 pt-3 text-sm leading-relaxed text-muted-foreground\",\n              \"rounded-b-xl\",\n              !isOpen && \"pointer-events-none\",\n            )}\n          >\n            {item.content}\n          </motion.div>\n        </div>\n      </motion.div>\n    </div>\n  );\n}\n"}],"meta":{"studioName":"GFA Development & Design Studio","siteUrl":"https://ui.gkhn.dev"}}