{
  "name": "accordion",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "accordion.tsx",
      "content": "\"use client\";\r\n\r\nimport { css, themeVars as theme } from \"@yugnex/core\";\r\nimport { useControllableState } from \"@yugnex/core/client\";\r\nimport {\r\n  createContext,\r\n  useCallback,\r\n  useContext,\r\n  useId,\r\n  useRef,\r\n  type CSSProperties,\r\n  type HTMLAttributes,\r\n  type KeyboardEvent,\r\n  type ReactNode,\r\n} from \"react\";\r\n\r\ninterface AccordionContextValue {\r\n  isOpen: (value: string) => boolean;\r\n  toggle: (value: string) => void;\r\n  register: (value: string, el: HTMLButtonElement | null) => void;\r\n  onTriggerKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => void;\r\n}\r\n\r\nconst AccordionContext = createContext<AccordionContextValue | null>(null);\r\n\r\nfunction useAccordionContext(component: string): AccordionContextValue {\r\n  const ctx = useContext(AccordionContext);\r\n  if (!ctx) throw new Error(`<${component}> must be used within <Accordion>`);\r\n  return ctx;\r\n}\r\n\r\nconst rootClass = css({\r\n  display: \"flex\",\r\n  flexDirection: \"column\",\r\n  borderRadius: theme.radius.md,\r\n  border: `1px solid ${theme.color.border}`,\r\n  overflow: \"hidden\",\r\n});\r\n\r\nconst itemClass = css({\r\n  \"& + &\": { borderTop: `1px solid ${theme.color.border}` },\r\n});\r\n\r\nconst triggerClass = css({\r\n  display: \"flex\",\r\n  alignItems: \"center\",\r\n  justifyContent: \"space-between\",\r\n  gap: theme.space[3],\r\n  width: \"100%\",\r\n  padding: `${theme.space[3]} ${theme.space[4]}`,\r\n  border: \"none\",\r\n  background: \"transparent\",\r\n  color: theme.color.foreground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  fontWeight: theme.fontWeight.medium,\r\n  textAlign: \"left\",\r\n  cursor: \"pointer\",\r\n  transitionProperty: \"background-color\",\r\n  transitionDuration: theme.duration.fast,\r\n  \"&:hover\": { backgroundColor: theme.color.muted },\r\n  \"&:focus-visible\": { outline: `2px solid ${theme.color.ring}`, outlineOffset: \"-2px\" },\r\n});\r\n\r\nconst chevronClass = css({\r\n  flexShrink: 0,\r\n  transitionProperty: \"transform\",\r\n  transitionDuration: theme.duration.base,\r\n  transitionTimingFunction: theme.easing.standard,\r\n  '[data-state=\"open\"] &': { transform: \"rotate(180deg)\" },\r\n});\r\n\r\nconst panelClass = css({\r\n  padding: `0 ${theme.space[4]} ${theme.space[4]}`,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  lineHeight: theme.lineHeight.base,\r\n  color: theme.color.mutedForeground,\r\n});\r\n\r\nexport interface AccordionProps {\r\n  /** \"single\" closes the previously open item; \"multiple\" lets several stay open. */\r\n  type?: \"single\" | \"multiple\";\r\n  value?: string[];\r\n  defaultValue?: string[];\r\n  onValueChange?: (value: string[]) => void;\r\n  /** In single mode, allow closing the open item by clicking it again. */\r\n  collapsible?: boolean;\r\n  children: ReactNode;\r\n  className?: string;\r\n  style?: CSSProperties;\r\n}\r\n\r\n/** A vertical set of collapsible sections with full arrow-key/Home/End navigation between headers. */\r\nexport function Accordion({\r\n  type = \"single\",\r\n  value,\r\n  defaultValue = [],\r\n  onValueChange,\r\n  collapsible = true,\r\n  children,\r\n  className,\r\n  style,\r\n}: AccordionProps) {\r\n  const [openValues, setOpenValues] = useControllableState<string[]>({\r\n    value,\r\n    defaultValue,\r\n    onChange: onValueChange,\r\n  });\r\n\r\n  // Insertion-ordered map of trigger nodes, used to move focus between headers.\r\n  const triggersRef = useRef<Map<string, HTMLButtonElement>>(new Map());\r\n\r\n  const register = useCallback((itemValue: string, el: HTMLButtonElement | null) => {\r\n    if (el) triggersRef.current.set(itemValue, el);\r\n    else triggersRef.current.delete(itemValue);\r\n  }, []);\r\n\r\n  const isOpen = useCallback((itemValue: string) => openValues.includes(itemValue), [openValues]);\r\n\r\n  const toggle = useCallback(\r\n    (itemValue: string) => {\r\n      const open = openValues.includes(itemValue);\r\n      if (type === \"multiple\") {\r\n        setOpenValues(open ? openValues.filter((v) => v !== itemValue) : [...openValues, itemValue]);\r\n        return;\r\n      }\r\n      if (open) setOpenValues(collapsible ? [] : openValues);\r\n      else setOpenValues([itemValue]);\r\n    },\r\n    [openValues, setOpenValues, type, collapsible],\r\n  );\r\n\r\n  const onTriggerKeyDown = useCallback((event: KeyboardEvent<HTMLButtonElement>) => {\r\n    const nodes = Array.from(triggersRef.current.values()).filter((el) => el.isConnected);\r\n    if (nodes.length === 0) return;\r\n    const index = nodes.indexOf(event.currentTarget);\r\n    if (index === -1) return;\r\n\r\n    let next: number | null = null;\r\n    if (event.key === \"ArrowDown\") next = (index + 1) % nodes.length;\r\n    else if (event.key === \"ArrowUp\") next = (index - 1 + nodes.length) % nodes.length;\r\n    else if (event.key === \"Home\") next = 0;\r\n    else if (event.key === \"End\") next = nodes.length - 1;\r\n\r\n    if (next !== null) {\r\n      event.preventDefault();\r\n      nodes[next]?.focus();\r\n    }\r\n  }, []);\r\n\r\n  return (\r\n    <AccordionContext.Provider value={{ isOpen, toggle, register, onTriggerKeyDown }}>\r\n      <div className={className ? `${rootClass} ${className}` : rootClass} style={style}>\r\n        {children}\r\n      </div>\r\n    </AccordionContext.Provider>\r\n  );\r\n}\r\n\r\nexport interface AccordionItemProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\r\n  value: string;\r\n  title: ReactNode;\r\n  children: ReactNode;\r\n}\r\n\r\nexport function AccordionItem({ value, title, children, className, ...props }: AccordionItemProps) {\r\n  const { isOpen, toggle, register, onTriggerKeyDown } = useAccordionContext(\"AccordionItem\");\r\n  const baseId = useId();\r\n  const triggerId = `${baseId}-trigger`;\r\n  const panelId = `${baseId}-panel`;\r\n  const open = isOpen(value);\r\n\r\n  return (\r\n    <div className={className ? `${itemClass} ${className}` : itemClass} data-state={open ? \"open\" : \"closed\"} {...props}>\r\n      <h3 style={{ margin: 0 }}>\r\n        <button\r\n          type=\"button\"\r\n          ref={(el) => register(value, el)}\r\n          id={triggerId}\r\n          className={triggerClass}\r\n          aria-expanded={open}\r\n          aria-controls={panelId}\r\n          onClick={() => toggle(value)}\r\n          onKeyDown={onTriggerKeyDown}\r\n        >\r\n          {title}\r\n          <svg className={chevronClass} width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" aria-hidden=\"true\">\r\n            <path d=\"M3 5.5L7 9.5L11 5.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\r\n          </svg>\r\n        </button>\r\n      </h3>\r\n      {open ? (\r\n        <div id={panelId} role=\"region\" aria-labelledby={triggerId} className={panelClass}>\r\n          {children}\r\n        </div>\r\n      ) : null}\r\n    </div>\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}