{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drawer-styled",
  "title": "Drawer essentials",
  "description": "Styled drawer: the essentials set. 5 decorative drawer variations (BottomDrawer, TopDrawer, GlassDrawer, RoundedDrawer, SnapDrawer) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/drawer-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Drawer family: 5 decorative bottom/top drawers. Each export is a complete drawer:\n   internal open/closed state (uncontrolled defaultOpen or controlled\n   open/onOpenChange), NO radix/vaul/portal - a self-contained fixed backdrop + a\n   fixed panel pinned to the edge, sliding in. It closes on a backdrop click, Escape\n   or a click on the grab handle. The panel takes focus on open. AnimatePresence\n   handles the slide; only a fade under reduced motion. There is a visible token\n   handle bar at the top/bottom end of the panel. size = panel height. Color comes\n   ONLY from tokens, via alpha color-mix. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst panelHeight: Record<StyledSize, string> = {\n  sm: \"h-[35vh]\",\n  md: \"h-[50vh]\",\n  lg: \"h-[68vh]\",\n  xl: \"h-[82vh]\",\n}\n\ninterface DrawerProps {\n  size?: StyledSize\n  trigger?: React.ReactNode\n  title?: React.ReactNode\n  children?: React.ReactNode\n  className?: string\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (o: boolean) => void\n}\n\ntype Edge = \"bottom\" | \"top\"\n\nconst backdropClass =\n  \"fixed inset-0 z-50 bg-[color-mix(in_oklab,var(--color-foreground)_50%,transparent)]\"\n\nconst panelBase =\n  \"fixed inset-x-0 z-50 flex flex-col border-border bg-card text-card-foreground shadow-lg outline-none\"\n\nconst triggerBtn =\n  \"inline-flex h-9 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-border bg-secondary px-4 text-sm font-medium text-secondary-foreground outline-none transition-colors hover:bg-[color-mix(in_oklab,var(--color-foreground)_6%,transparent)] focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\nconst handleBtn =\n  \"flex w-full shrink-0 cursor-pointer items-center justify-center py-3 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-inset\"\n\nconst handleBar =\n  \"h-1.5 w-12 rounded-full bg-[color-mix(in_oklab,var(--color-foreground)_25%,transparent)]\"\n\n/* Controlled/uncontrolled acik durum yonetimi. */\nfunction useDrawerState(props: DrawerProps) {\n  const { open, defaultOpen, onOpenChange } = props\n  const isControlled = open !== undefined\n  const [internal, setInternal] = React.useState(defaultOpen ?? false)\n  const isOpen = isControlled ? open : internal\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (!isControlled) setInternal(next)\n      onOpenChange?.(next)\n    },\n    [isControlled, onOpenChange]\n  )\n\n  return { isOpen, setOpen }\n}\n\n/* Shared shell: trigger plus an AnimatePresence backdrop plus a panel pinned to the edge. edge determines the placement, the handle position and the slide direction. */\nfunction DrawerShell({\n  props,\n  edge,\n  heightClass,\n  panelClassName,\n  backdropClassName,\n}: {\n  props: DrawerProps\n  edge: Edge\n  heightClass?: string\n  panelClassName?: string\n  backdropClassName?: string\n}) {\n  const { size = \"md\", trigger, title, children, className } = props\n  const { isOpen, setOpen } = useDrawerState(props)\n  const reduce = useReducedMotion()\n  const panelRef = React.useRef<HTMLDivElement>(null)\n\n  React.useEffect(() => {\n    if (!isOpen) return\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setOpen(false)\n    }\n    window.addEventListener(\"keydown\", onKey)\n    const id = window.requestAnimationFrame(() => panelRef.current?.focus())\n    return () => {\n      window.removeEventListener(\"keydown\", onKey)\n      window.cancelAnimationFrame(id)\n    }\n  }, [isOpen, setOpen])\n\n  const offscreen = edge === \"bottom\" ? \"100%\" : \"-100%\"\n  const slide = {\n    initial: { y: offscreen },\n    animate: { y: 0 },\n    exit: { y: offscreen },\n  }\n  const fade = { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } }\n  const motionProps = reduce ? fade : slide\n\n  const handle = (\n    <button\n      type=\"button\"\n      aria-label=\"Close drawer\"\n      className={handleBtn}\n      onClick={() => setOpen(false)}\n    >\n      <span className={handleBar} />\n    </button>\n  )\n\n  return (\n    <>\n      {/* The ARIA state lives on the trigger ITSELF. This file never had a `role=\"button\"` wrapper, but it had no aria-haspopup or aria-expanded either: a screen-reader user had NO idea the button opened a dialog or whether it was open. It was caught during a live AX measurement - it was missing from the first list because the file was not found by a `role=\"button\"` search. */}\n      <span data-slot=\"styled-drawer-trigger\" className=\"inline-flex\">\n        {React.isValidElement<React.ButtonHTMLAttributes<HTMLButtonElement>>(trigger) ? (\n          React.cloneElement(trigger, {\n            \"aria-haspopup\": \"dialog\",\n            \"aria-expanded\": isOpen,\n            onClick: (event: React.MouseEvent<HTMLButtonElement>) => {\n              trigger.props.onClick?.(event)\n              setOpen(true)\n            },\n          })\n        ) : (\n          <button\n            type=\"button\"\n            aria-haspopup=\"dialog\"\n            aria-expanded={isOpen}\n            onClick={() => setOpen(true)}\n            className={triggerBtn}\n          >\n            {trigger ?? \"Open\"}\n          </button>\n        )}\n      </span>\n\n      <AnimatePresence>\n        {isOpen ? (\n          <motion.div\n            data-slot=\"styled-drawer-backdrop\"\n            className={cn(backdropClass, backdropClassName)}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.2, ease: \"easeOut\" }}\n            onClick={() => setOpen(false)}\n          >\n            <motion.div\n              ref={panelRef}\n              data-slot=\"styled-drawer\"\n              role=\"dialog\"\n              aria-modal=\"true\"\n              tabIndex={-1}\n              className={cn(panelBase, edge === \"bottom\" ? \"bottom-0\" : \"top-0\", heightClass ?? panelHeight[size], panelClassName, className)}\n              initial={motionProps.initial}\n              animate={motionProps.animate}\n              exit={motionProps.exit}\n              transition={reduce ? { duration: 0.2, ease: \"easeOut\" } : { type: \"spring\", stiffness: 320, damping: 34 }}\n              onClick={(e) => e.stopPropagation()}\n            >\n              {edge === \"bottom\" ? handle : null}\n              {title ? (\n                <div className=\"shrink-0 px-6 pb-2 pt-1 text-base font-semibold\">{title}</div>\n              ) : null}\n              <div className=\"flex-1 overflow-y-auto px-6 py-4 text-sm text-muted-foreground\">\n                {children}\n              </div>\n              {edge === \"top\" ? handle : null}\n            </motion.div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </>\n  )\n}\n\n/* Bottom: alt kenardan yukari kayar, ust koseleri yuvarlak, ustte tutamac. */\nexport function BottomDrawer({\n  size = \"md\",\n  trigger,\n  title,\n  children,\n  className,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: DrawerProps) {\n  return (\n    <DrawerShell\n      props={{ size, trigger, title, children, className, open, defaultOpen, onOpenChange }}\n      edge=\"bottom\"\n      panelClassName=\"rounded-t-2xl border-t\"\n    />\n  )\n}\n\n/* Top: ust kenardan asagi kayar, alt koseleri yuvarlak, altta tutamac. */\nexport function TopDrawer({\n  size = \"md\",\n  trigger,\n  title,\n  children,\n  className,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: DrawerProps) {\n  return (\n    <DrawerShell\n      props={{ size, trigger, title, children, className, open, defaultOpen, onOpenChange }}\n      edge=\"top\"\n      panelClassName=\"rounded-b-2xl border-b\"\n    />\n  )\n}\n\n/* Glass: alt cekmece, buzlu cam panel + backdrop-blur. */\nexport function GlassDrawer({\n  size = \"md\",\n  trigger,\n  title,\n  children,\n  className,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: DrawerProps) {\n  return (\n    <DrawerShell\n      props={{ size, trigger, title, children, className, open, defaultOpen, onOpenChange }}\n      edge=\"bottom\"\n      backdropClassName=\"bg-[color-mix(in_oklab,var(--color-foreground)_40%,transparent)] supports-[backdrop-filter]:backdrop-blur-sm\"\n      panelClassName=\"rounded-t-2xl border-t bg-[color-mix(in_oklab,var(--color-card)_75%,transparent)] supports-[backdrop-filter]:bg-[color-mix(in_oklab,var(--color-card)_55%,transparent)] supports-[backdrop-filter]:backdrop-blur-xl shadow-[inset_0_1px_0_color-mix(in_oklab,var(--color-foreground)_12%,transparent)]\"\n    />\n  )\n}\n\n/* Rounded: alt cekmece, kenarlardan bosluklu yuzen panel, cok yuvarlak koseler. */\nexport function RoundedDrawer({\n  size = \"md\",\n  trigger,\n  title,\n  children,\n  className,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: DrawerProps) {\n  return (\n    <DrawerShell\n      props={{ size, trigger, title, children, className, open, defaultOpen, onOpenChange }}\n      edge=\"bottom\"\n      panelClassName=\"inset-x-3 bottom-3 rounded-3xl border\"\n    />\n  )\n}\n\n/* Snap: a bottom drawer, a longer panel that reads like a snap point (open/close\n   only). */\nexport function SnapDrawer({\n  size = \"md\",\n  trigger,\n  title,\n  children,\n  className,\n  open,\n  defaultOpen,\n  onOpenChange,\n}: DrawerProps) {\n  return (\n    <DrawerShell\n      props={{ size, trigger, title, children, className, open, defaultOpen, onOpenChange }}\n      edge=\"bottom\"\n      heightClass=\"h-[88vh]\"\n      panelClassName=\"rounded-t-2xl border-t\"\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/drawer-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "drawer"
  ],
  "type": "registry:component"
}