{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collapsible",
  "title": "Collapsible",
  "description": "One disclosure: a label that opens the detail under it, for the one thing most readers can skip.",
  "dependencies": [
    "radix-ui",
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/collapsible.tsx",
      "content": "import type * as React from \"react\"\nimport { useContext, useMemo, useState } from \"react\"\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { ChevronDown, ChevronRight } from \"lucide-react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport {\n  CollapsibleContext,\n  ROOT_CLASS,\n  TRIGGER_CLASS,\n  type CollapsibleEffect,\n  type CollapsibleVariant,\n} from \"@/registry/miseui/ui/collapsible-classes\"\nimport { CollapsibleContent, type CollapsibleContentProps } from \"@/registry/miseui/ui/collapsible-content\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst INSTANT = { duration: 0 } as const\n\ntype CollapsibleProps = React.ComponentProps<typeof CollapsiblePrimitive.Root> & {\n  /** `plain` a bare row, `card` a bordered surface, `fade` long text fading into Show more, `inline` a quiet text link. */\n  variant?: CollapsibleVariant\n  /** `spring` animates height from wherever it is now, so it can reverse mid-flight. */\n  effect?: CollapsibleEffect\n}\n\nfunction Collapsible({\n  variant = \"plain\",\n  effect,\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n  ...props\n}: CollapsibleProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const [uncontrolled, setUncontrolled] = useState(defaultOpen)\n  const current = open ?? uncontrolled\n  const context = useMemo(\n    () => ({ variant, effect, open: current, reduceMotion }),\n    [variant, effect, current, reduceMotion],\n  )\n  return (\n    <CollapsibleContext.Provider value={context}>\n      <CollapsiblePrimitive.Root\n        data-slot=\"collapsible\"\n        data-variant={variant}\n        data-effect={effect}\n        className={cn(ROOT_CLASS[variant], className)}\n        open={current}\n        onOpenChange={(next) => {\n          setUncontrolled(next)\n          onOpenChange?.(next)\n        }}\n        {...props}\n      />\n    </CollapsibleContext.Provider>\n  )\n}\n\nfunction CollapsibleIcon() {\n  const { variant, effect, open, reduceMotion } = useContext(CollapsibleContext)\n  const isInline = variant === \"inline\"\n  const Icon = isInline ? ChevronRight : ChevronDown\n  if (effect === \"spring\") {\n    return (\n      <motion.span\n        aria-hidden=\"true\"\n        className=\"flex shrink-0 text-muted-foreground\"\n        initial={false}\n        animate={{ rotate: open ? (isInline ? 90 : 180) : 0 }}\n        transition={reduceMotion ? INSTANT : SPRING_DEFAULT}\n      >\n        <Icon className=\"size-4\" />\n      </motion.span>\n    )\n  }\n  return (\n    <span aria-hidden=\"true\" className=\"flex shrink-0 text-muted-foreground\">\n      <Icon\n        className={cn(\n          \"size-4 transition-transform duration-(--duration-base) ease-(--ease-mise) motion-reduce:transition-none\",\n          isInline ? \"group-data-[state=open]/trigger:rotate-90\" : \"group-data-[state=open]/trigger:rotate-180\",\n        )}\n      />\n    </span>\n  )\n}\n\ntype CollapsibleTriggerProps = React.ComponentProps<typeof CollapsiblePrimitive.Trigger> & {\n  /** Used when the trigger has no children. */\n  moreLabel?: string\n  lessLabel?: string\n}\n\nfunction CollapsibleTrigger({\n  className,\n  children,\n  moreLabel = \"Show more\",\n  lessLabel = \"Show less\",\n  ...props\n}: CollapsibleTriggerProps) {\n  const { variant, open } = useContext(CollapsibleContext)\n  return (\n    <CollapsiblePrimitive.Trigger\n      data-slot=\"collapsible-trigger\"\n      className={cn(\n        \"group/trigger rounded-sm text-left outline-none transition-colors duration-(--duration-fast) ease-(--ease-mise)\",\n        \"focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50\",\n        TRIGGER_CLASS[variant],\n        className,\n      )}\n      {...props}\n    >\n      {children ?? (open ? lessLabel : moreLabel)}\n      <CollapsibleIcon />\n    </CollapsiblePrimitive.Trigger>\n  )\n}\n\nexport {\n  Collapsible,\n  CollapsibleTrigger,\n  CollapsibleContent,\n  type CollapsibleProps,\n  type CollapsibleTriggerProps,\n  type CollapsibleContentProps,\n  type CollapsibleVariant,\n  type CollapsibleEffect,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/collapsible-content.tsx",
      "content": "import type * as React from \"react\"\nimport { useContext } from \"react\"\nimport { Collapsible as CollapsiblePrimitive } from \"radix-ui\"\nimport { motion } from \"motion/react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport {\n  CollapsibleContext,\n  DEFAULT_COLLAPSED_HEIGHT_PX,\n  FADE_MASK_CLASS,\n  FADE_STOP_CLOSED,\n  FADE_STOP_OPEN,\n  PANEL_CLASS,\n} from \"@/registry/miseui/ui/collapsible-classes\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst INSTANT = { duration: 0 } as const\n\ntype CollapsibleContentProps = Omit<React.ComponentProps<typeof CollapsiblePrimitive.Content>, \"forceMount\"> & {\n  /** `fade` only: how much of the text stays visible while closed. */\n  collapsedHeight?: number\n}\n\ntype PanelProps = CollapsibleContentProps & { collapsedHeight: number }\n\n/** Fade without the spring: the preview is a fixed height, so it needs no measuring and stays mounted to stay visible. */\nfunction FadeContent({ children, collapsedHeight, ...props }: PanelProps) {\n  const { open } = useContext(CollapsibleContext)\n  const closedStyle = { height: collapsedHeight, \"--collapsible-fade\": FADE_STOP_CLOSED } as React.CSSProperties\n  return (\n    <CollapsiblePrimitive.Content\n      forceMount\n      inert={!open}\n      data-slot=\"collapsible-content\"\n      className={cn(\"overflow-hidden\", !open && FADE_MASK_CLASS)}\n      style={open ? undefined : closedStyle}\n      {...props}\n    >\n      {children}\n    </CollapsiblePrimitive.Content>\n  )\n}\n\nfunction SpringContent({ children, collapsedHeight, ...props }: PanelProps) {\n  const { variant, open, reduceMotion } = useContext(CollapsibleContext)\n  const isFade = variant === \"fade\"\n  const height = open ? \"auto\" : isFade ? collapsedHeight : 0\n  // forceMount keeps the panel in the DOM so the spring can run both ways, so inert is what hides it from the tab order.\n  return (\n    <CollapsiblePrimitive.Content forceMount inert={!open} data-slot=\"collapsible-content\" {...props}>\n      <motion.div\n        className={cn(\"overflow-hidden\", isFade && FADE_MASK_CLASS)}\n        initial={false}\n        animate={isFade ? { height, \"--collapsible-fade\": open ? FADE_STOP_OPEN : FADE_STOP_CLOSED } : { height }}\n        transition={reduceMotion ? INSTANT : SPRING_DEFAULT}\n      >\n        {children}\n      </motion.div>\n    </CollapsiblePrimitive.Content>\n  )\n}\n\nfunction CollapsibleContent({\n  className,\n  children,\n  collapsedHeight = DEFAULT_COLLAPSED_HEIGHT_PX,\n  ...props\n}: CollapsibleContentProps) {\n  const { variant, effect } = useContext(CollapsibleContext)\n  const panel = <div className={cn(PANEL_CLASS[variant], className)}>{children}</div>\n  if (effect === \"spring\") {\n    return (\n      <SpringContent collapsedHeight={collapsedHeight} {...props}>\n        {panel}\n      </SpringContent>\n    )\n  }\n  if (variant === \"fade\") {\n    return (\n      <FadeContent collapsedHeight={collapsedHeight} {...props}>\n        {panel}\n      </FadeContent>\n    )\n  }\n  return (\n    <CollapsiblePrimitive.Content\n      data-slot=\"collapsible-content\"\n      className=\"overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down motion-reduce:animate-none\"\n      {...props}\n    >\n      {panel}\n    </CollapsiblePrimitive.Content>\n  )\n}\n\nexport { CollapsibleContent, type CollapsibleContentProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/collapsible-classes.ts",
      "content": "import { createContext } from \"react\"\n\nexport type CollapsibleVariant = \"plain\" | \"card\" | \"fade\" | \"inline\"\nexport type CollapsibleEffect = \"spring\"\n\ninterface CollapsibleContextValue {\n  variant: CollapsibleVariant\n  effect: CollapsibleEffect | undefined\n  open: boolean\n  reduceMotion: boolean\n}\n\nexport const CollapsibleContext = createContext<CollapsibleContextValue>({\n  variant: \"plain\",\n  effect: undefined,\n  open: false,\n  reduceMotion: false,\n})\n\nexport const DEFAULT_COLLAPSED_HEIGHT_PX = 96\n/** Where the text starts to fade; 100% means no fade at all, so the same mask serves both states. */\nexport const FADE_STOP_CLOSED = \"40%\"\nexport const FADE_STOP_OPEN = \"100%\"\nexport const FADE_MASK_CLASS = \"[mask-image:linear-gradient(to_bottom,black_var(--collapsible-fade),transparent)]\"\n\nexport const ROOT_CLASS: Record<CollapsibleVariant, string> = {\n  plain: \"w-full\",\n  card: \"w-full rounded-lg border border-border bg-card text-card-foreground\",\n  fade: \"w-full\",\n  inline: \"inline-flex max-w-full flex-col items-start\",\n}\n\nexport const TRIGGER_CLASS: Record<CollapsibleVariant, string> = {\n  plain: \"flex min-h-10 w-full items-center justify-between gap-4 text-sm font-medium hover:text-foreground/70\",\n  card: \"flex min-h-11 w-full items-center justify-between gap-4 rounded-lg px-4 py-3 text-sm font-medium hover:bg-accent/60\",\n  fade: \"inline-flex min-h-10 items-center gap-1 text-sm font-medium hover:text-foreground/70\",\n  inline: \"inline-flex min-h-10 flex-row-reverse items-center gap-1 text-sm text-muted-foreground hover:text-foreground\",\n}\n\nexport const PANEL_CLASS: Record<CollapsibleVariant, string> = {\n  plain: \"pb-2 text-sm/6 text-muted-foreground\",\n  card: \"px-4 pb-4 text-sm/6 text-muted-foreground\",\n  fade: \"text-sm/6 text-muted-foreground\",\n  inline: \"ml-2 border-l border-border py-1 pl-3 text-sm/6 text-muted-foreground\",\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}