{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drawer",
  "type": "registry:ui",
  "title": "Drawer",
  "description": "React Aria backed modal and non-modal push drawer/sheet primitive covering all four edge directions, with direction-aware size/fullSize/dimension sizing, provider-aware portals, token-backed overlay/content styling, controlled open state, dismissal controls, spring-driven open/close translation, drag-to-dismiss, snap points, a draggable handle, an iOS-style backgroundScale page wrapper, opt-in edge-swipe-to-open, nested/stacked drawer auto-recede, a layoutId shared-element passthrough, none/subtle/standard/expressive motion presets, and reduced-motion fallbacks. Motion is a required runtime dependency; the core open/close/dismiss path stays fully functional when drag and spring polish are disabled by reduced motion or motionPreset=\"none\".",
  "dependencies": [
    "motion@^12.43.0",
    "react-aria@^3.52.1",
    "react-aria-components@^1.21.1"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json",
    "https://components.dethink.co.uk/r/button.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/drawer/drawer.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/drawer.tsx",
      "content": "import {\n  Button as AriaButton,\n  Dialog as AriaDialog,\n  Modal,\n  ModalOverlay,\n  type ButtonProps as AriaButtonProps,\n  type DialogRenderProps as AriaDialogRenderProps,\n  type ModalOverlayProps as AriaModalOverlayProps,\n} from \"react-aria-components\";\nimport {\n  createContext,\n  forwardRef,\n  type CSSProperties,\n  type HTMLAttributes,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n  type Ref,\n  type RefObject,\n  useContext,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  buttonClassNames,\n  type ButtonSize,\n  type ButtonVariant,\n} from \"../button\";\nimport {\n  DethinkPortalProvider,\n  useProviderPortalRoot,\n} from \"../../utils/provider-portal\";\nimport { cn } from \"../../utils/cn\";\nimport {\n  DRAWER_DEFAULT_CLOSE_THRESHOLD,\n  DRAWER_DEFAULT_VELOCITY_THRESHOLD,\n  type DrawerSnapPoint,\n} from \"./drawer-snap\";\nimport {\n  DRAWER_DEFAULT_EDGE_SWIPE_HIT_REGION_SIZE,\n  DrawerEdgeSwipeZone,\n  DrawerMotionHandle,\n  MotionDiv,\n  MotionModal,\n  drawerHandleClassNames,\n  getDrawerMotionPresetSettings,\n  shouldEnableDrawerMotion,\n  useDrawerDrag,\n  useDrawerReducedMotion,\n  type DragControls,\n} from \"./drawer-motion\";\nimport { useDrawerBackgroundScale } from \"./drawer-background-scale\";\nimport type { DrawerDirection, DrawerMotionPreset } from \"./drawer-types\";\n\nexport type { DrawerDirection, DrawerMotionPreset } from \"./drawer-types\";\nexport type { DrawerSnapPoint } from \"./drawer-snap\";\nexport type DrawerDimension = number | string;\nexport type DrawerSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\nexport type DrawerScrollBehavior = \"inside\" | \"outside\";\n\nexport interface DrawerCloseRenderProps {\n  close: () => void;\n}\n\nexport interface DrawerProps {\n  \"data-slot\"?: string;\n  activeSnapPoint?: number;\n  backgroundScale?: boolean;\n  children?: ReactNode;\n  className?: string;\n  closeThreshold?: number;\n  defaultOpen?: boolean;\n  defaultSnapPoint?: number;\n  dimension?: DrawerDimension;\n  direction?: DrawerDirection;\n  dragHandleOnly?: boolean;\n  edgeSwipeHitRegionSize?: number;\n  edgeSwipeToOpen?: boolean;\n  fullSize?: boolean;\n  modal?: boolean;\n  motionPreset?: DrawerMotionPreset;\n  onActiveSnapPointChange?: (snapPoint: number) => void;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  reducedMotion?: boolean;\n  size?: DrawerSize;\n  snapPoints?: DrawerSnapPoint[];\n  velocityThreshold?: number;\n}\n\nexport interface DrawerTriggerProps extends Omit<\n  AriaButtonProps,\n  \"children\" | \"className\"\n> {\n  \"data-slot\"?: string;\n  children?: ReactNode;\n  className?: string;\n  size?: ButtonSize;\n  variant?: ButtonVariant;\n}\n\nexport interface DrawerContentProps extends Omit<\n  HTMLAttributes<HTMLDivElement>,\n  | \"children\"\n  | \"className\"\n  | \"onAnimationEnd\"\n  | \"onAnimationIteration\"\n  | \"onAnimationStart\"\n  | \"onDrag\"\n  | \"onDragEnd\"\n  | \"onDragEnter\"\n  | \"onDragLeave\"\n  | \"onDragOver\"\n  | \"onDragStart\"\n  | \"onDrop\"\n  | \"onTransitionCancel\"\n  | \"onTransitionEnd\"\n  | \"onTransitionRun\"\n  | \"onTransitionStart\"\n> {\n  \"aria-describedby\"?: string;\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string;\n  children?: ReactNode | ((opts: DrawerCloseRenderProps) => ReactNode);\n  className?: string;\n  closeButtonClassName?: string;\n  closeButtonLabel?: string;\n  dismissible?: boolean;\n  dimension?: DrawerDimension;\n  fullSize?: boolean;\n  keyboardDismissDisabled?: boolean;\n  layoutId?: string;\n  overlayClassName?: string;\n  scrollBehavior?: DrawerScrollBehavior;\n  shouldCloseOnInteractOutside?: AriaModalOverlayProps[\"shouldCloseOnInteractOutside\"];\n  showCloseButton?: boolean;\n  size?: DrawerSize;\n}\n\nexport interface DrawerHeaderProps extends HTMLAttributes<HTMLDivElement> {}\n\nexport interface DrawerFooterProps extends HTMLAttributes<HTMLDivElement> {}\n\nexport interface DrawerTitleProps extends Omit<\n  HTMLAttributes<HTMLHeadingElement>,\n  \"className\"\n> {\n  className?: string;\n  level?: 1 | 2 | 3 | 4 | 5 | 6;\n  visuallyHidden?: boolean;\n}\n\nexport interface DrawerDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {}\n\nexport interface DrawerCloseProps extends DrawerTriggerProps {}\n\nexport interface DrawerHandleProps extends HTMLAttributes<HTMLDivElement> {}\n\ninterface DrawerRootContextValue {\n  nested: boolean;\n  activeSnapPoint?: number;\n  backgroundScale: boolean;\n  closeThreshold: number;\n  defaultSnapPoint?: number;\n  dimension?: DrawerDimension;\n  direction: DrawerDirection;\n  dragHandleOnly: boolean;\n  fullSize: boolean;\n  modal: boolean;\n  motionPreset: DrawerMotionPreset;\n  onActiveSnapPointChange?: (snapPoint: number) => void;\n  onOpenChange: (open: boolean) => void;\n  open: boolean;\n  portalContainer: HTMLElement | null;\n  reducedMotion?: boolean;\n  setTriggerElement: (element: HTMLButtonElement | null) => void;\n  size: DrawerSize;\n  snapPoints?: DrawerSnapPoint[];\n  velocityThreshold: number;\n}\n\nconst DrawerRootContext = createContext<DrawerRootContextValue | null>(null);\n\n/**\n * Provided by an open drawer's content to any `Drawer` rendered inside it.\n * A nested `Drawer` registers itself here while its own `open` is true, so\n * the parent can recede (see `useDrawerDrag`'s `receded` option) using the\n * same spring primitives as the base drag layer — no separate animation\n * path, and no DOM-tree walking, since context already pierces the child's\n * portal.\n *\n * `portalContainer` is the parent's own resolved portal container (which\n * may itself be inherited from a further-out ancestor). A nested `Drawer`\n * portals into the SAME container instead of creating its own body-level\n * one: React Aria's modal background-inertness (`ariaHideOutside`) hides\n * everything outside a modal's own DOM subtree, and two `ModalOverlay`\n * instances mounted into two separate top-level containers do not reliably\n * exempt each other, which made a nested drawer's own dialog get hidden by\n * its parent's inertness sweep the moment it opened. Sharing one container\n * keeps the whole nested chain as a single DOM subtree, which is the\n * pattern React Aria's own nested-modal usage expects.\n */\ninterface DrawerNestedContextValue {\n  portalContainer: HTMLElement | null;\n  registerChildOpen: () => void;\n  unregisterChildOpen: () => void;\n}\n\nconst DrawerNestedContext = createContext<DrawerNestedContextValue | null>(\n  null,\n);\n\ninterface DrawerContentContextValue {\n  defaultTitleId: string;\n  dragControls: DragControls | null;\n  hasCloseButton: boolean;\n  motionEnabled: boolean;\n  setDescriptionId: (id: string | null) => void;\n  setTitleId: (id: string | null) => void;\n}\n\nconst DrawerContentContext = createContext<DrawerContentContextValue | null>(\n  null,\n);\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nconst drawerDirectionAxis: Record<DrawerDirection, \"horizontal\" | \"vertical\"> =\n  {\n    bottom: \"vertical\",\n    left: \"horizontal\",\n    right: \"horizontal\",\n    top: \"vertical\",\n  };\n\nconst drawerRootClasses = \"contents\";\n\nconst drawerOverlayBaseClasses =\n  \"fixed inset-0 z-50 bg-[color-mix(in_srgb,var(--dt-overlay-scrim)_75%,transparent)] outline-none motion-safe:transition-opacity data-[entering]:opacity-0 data-[exiting]:opacity-0 data-[nested=true]:bg-transparent\";\n\nconst drawerContentBaseClasses =\n  \"fixed z-50 flex max-h-dvh max-w-full flex-col border-border/80 bg-background text-foreground shadow-xl outline-none will-change-transform motion-safe:transition-[opacity,filter] motion-safe:ease-out data-[motion=none]:transition-none data-[reduced-motion=true]:transition-none\";\n\n/**\n * Dims a receded (auto-parented-back) drawer. Always present in the class\n * list — it is inert unless `data-drawer-receded=\"true\"` is set — so it\n * applies identically whether or not Motion is driving the recede scale\n * transform (which is a separate, JS-driven `transform`, so it never\n * fights this `filter`-only CSS rule).\n */\nconst drawerContentRecedeClasses = \"data-[drawer-receded=true]:brightness-90\";\n\nconst drawerContentPositionClasses: Record<DrawerDirection, string> = {\n  bottom:\n    \"inset-x-0 bottom-0 origin-bottom rounded-t-2xl border-t data-[entering]:translate-y-0 data-[exiting]:translate-y-full data-[exiting]:opacity-0\",\n  left: \"inset-y-0 left-0 origin-left rounded-r-lg border-r data-[entering]:translate-x-0 data-[exiting]:-translate-x-full data-[exiting]:opacity-0\",\n  right:\n    \"inset-y-0 right-0 origin-right rounded-l-lg border-l data-[entering]:translate-x-0 data-[exiting]:translate-x-full data-[exiting]:opacity-0\",\n  top: \"inset-x-0 top-0 origin-top rounded-b-2xl border-b data-[entering]:translate-y-0 data-[exiting]:-translate-y-full data-[exiting]:opacity-0\",\n};\n\nconst drawerPushContainerBaseClasses =\n  \"relative flex shrink-0 border-border/80 bg-background/95 text-foreground shadow-[0_18px_48px_-32px_rgb(0_0_0/0.4)] backdrop-blur motion-safe:transition-[width,height,opacity,filter] motion-safe:ease-out\";\n\nconst drawerPushBorderClasses: Record<DrawerDirection, string> = {\n  bottom: \"border-t\",\n  left: \"border-r\",\n  right: \"border-l\",\n  top: \"border-b\",\n};\n\nconst drawerVerticalSizeClasses: Record<DrawerSize, string> = {\n  full: \"h-[calc(100dvh-(env(safe-area-inset-top)+env(safe-area-inset-bottom)))]\",\n  lg: \"h-[75dvh]\",\n  md: \"h-[50dvh]\",\n  sm: \"h-[35dvh]\",\n  xl: \"h-[90dvh]\",\n};\n\nconst drawerHorizontalSizeClasses: Record<DrawerSize, string> = {\n  full: \"w-[calc(100vw-(env(safe-area-inset-left)+env(safe-area-inset-right)))]\",\n  lg: \"w-[32rem]\",\n  md: \"w-96\",\n  sm: \"w-80\",\n  xl: \"w-[40rem]\",\n};\n\nconst drawerCustomSizeClasses: Record<\"horizontal\" | \"vertical\", string> = {\n  horizontal: \"w-[var(--drawer-size)]\",\n  vertical: \"h-[var(--drawer-size)]\",\n};\n\nconst drawerContentScrollBehaviorClasses: Record<DrawerScrollBehavior, string> =\n  {\n    inside: \"overflow-y-auto overscroll-contain\",\n    outside: \"overflow-visible\",\n  };\n\n// The dialog needs a real box so native focus and Escape dismissal work on open.\nconst drawerPanelClasses =\n  \"relative flex min-h-0 min-w-0 flex-1 flex-col outline-none\";\n\nfunction drawerSnapPanelClassNames(direction: DrawerDirection) {\n  switch (direction) {\n    case \"bottom\":\n      return \"h-[var(--drawer-visible-size)] flex-none overflow-y-auto overscroll-contain\";\n    case \"top\":\n      return \"mt-auto h-[var(--drawer-visible-size)] flex-none overflow-y-auto overscroll-contain\";\n    case \"left\":\n      return \"ml-auto h-full w-[var(--drawer-visible-size)] flex-none overflow-y-auto overscroll-contain\";\n    case \"right\":\n      return \"h-full w-[var(--drawer-visible-size)] flex-none overflow-y-auto overscroll-contain\";\n  }\n}\n\nconst drawerHeaderClasses =\n  \"grid shrink-0 gap-[var(--dt-space-1-5)] border-b border-border/60 bg-background/95 p-[var(--dt-space-6)] pb-[var(--dt-space-3)] text-start backdrop-blur\";\n\nconst drawerHeaderWithCloseButtonClasses =\n  \"pe-[calc(var(--dt-space-6)+var(--dt-space-8))]\";\n\nconst drawerFooterClasses =\n  \"mt-auto flex shrink-0 flex-col-reverse gap-density-gap border-t border-border/60 bg-background p-[var(--dt-space-6)] pt-[var(--dt-space-3)] pb-[max(var(--dt-space-4),env(safe-area-inset-bottom))] sm:flex-row sm:items-center sm:justify-end\";\n\nconst drawerTitleClasses =\n  \"text-lg font-semibold leading-7 tracking-normal text-foreground\";\n\nconst drawerDescriptionClasses = \"text-sm leading-6 text-muted-foreground\";\n\nconst visuallyHiddenClasses = \"sr-only\";\n\nconst drawerCloseIconClasses = \"pointer-events-none size-4 shrink-0\";\n\nconst drawerCloseButtonClasses =\n  \"absolute end-[var(--dt-space-3)] top-[var(--dt-space-3)] z-10 bg-background/80 shadow-sm backdrop-blur\";\n\nconst drawerFocusableSelector = [\n  \"a[href]\",\n  \"area[href]\",\n  \"button:not([disabled])\",\n  \"input:not([disabled])\",\n  \"select:not([disabled])\",\n  \"textarea:not([disabled])\",\n  \"summary\",\n  \"[contenteditable='true']\",\n  \"audio[controls]\",\n  \"video[controls]\",\n  \"[tabindex]:not([tabindex='-1'])\",\n].join(\",\");\n\nconst drawerPreviousTabIndexAttribute = \"data-drawer-previous-tab-index\";\n\ntype DrawerContentStyle = CSSProperties & {\n  \"--drawer-size\"?: string;\n};\n\nfunction joinIds(...ids: Array<string | undefined>) {\n  return ids.filter(Boolean).join(\" \") || undefined;\n}\n\nfunction assignRef<T>(ref: Ref<T> | undefined, value: T | null) {\n  if (typeof ref === \"function\") {\n    ref(value);\n  } else if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction composeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      assignRef(ref, node);\n    }\n  };\n}\n\nfunction formatDrawerDimension(dimension: DrawerDimension | undefined) {\n  if (dimension === undefined) {\n    return undefined;\n  }\n\n  return typeof dimension === \"number\" ? `${dimension}px` : dimension;\n}\n\nfunction drawerContentStyle({\n  dimension,\n  style,\n}: {\n  dimension?: DrawerDimension;\n  style?: CSSProperties;\n}) {\n  const formattedDimension = formatDrawerDimension(dimension);\n\n  if (!formattedDimension) {\n    return style;\n  }\n\n  return {\n    ...style,\n    \"--drawer-size\": formattedDimension,\n  } satisfies DrawerContentStyle;\n}\n\nfunction resolveDrawerContentSizing({\n  dimension,\n  fullSize,\n  rootContext,\n  size,\n}: {\n  dimension?: DrawerDimension;\n  fullSize?: boolean;\n  rootContext: DrawerRootContextValue | null;\n  size?: DrawerSize;\n}) {\n  const resolvedSize = size ?? rootContext?.size ?? \"md\";\n\n  if (dimension !== undefined) {\n    return {\n      dimension,\n      fullSize: false,\n      size: resolvedSize,\n    };\n  }\n\n  if (fullSize !== undefined) {\n    return {\n      dimension: fullSize ? undefined : rootContext?.dimension,\n      fullSize,\n      size: resolvedSize,\n    };\n  }\n\n  if (rootContext?.dimension !== undefined) {\n    return {\n      dimension: rootContext.dimension,\n      fullSize: false,\n      size: resolvedSize,\n    };\n  }\n\n  return {\n    dimension: undefined,\n    fullSize: rootContext?.fullSize ?? false,\n    size: resolvedSize,\n  };\n}\n\nconst drawerHorizontalFallbackSizePx: Record<\n  Exclude<DrawerSize, \"full\">,\n  number\n> = {\n  lg: 512,\n  md: 384,\n  sm: 320,\n  xl: 640,\n};\n\nconst drawerVerticalFallbackSizeRatio: Record<DrawerSize, number> = {\n  full: 1,\n  lg: 0.75,\n  md: 0.5,\n  sm: 0.35,\n  xl: 0.9,\n};\n\nfunction parseDrawerDimensionFallback(dimension: DrawerDimension | undefined) {\n  if (dimension === undefined || typeof window === \"undefined\") {\n    return undefined;\n  }\n\n  if (typeof dimension === \"number\") {\n    return dimension;\n  }\n\n  // Keep integer and fractional digits separated by a required decimal point\n  // so a long invalid number cannot be repartitioned between digit groups.\n  const match = dimension\n    .trim()\n    .toLowerCase()\n    .match(/^(-?(?:\\d+(?:\\.\\d+)?|\\.\\d+))\\s*(px|rem|vw|vh|dvh|svh|lvh)$/);\n\n  if (!match) {\n    return undefined;\n  }\n\n  const value = Number.parseFloat(match[1]);\n  const unit = match[2];\n\n  if (unit === \"px\") {\n    return value;\n  }\n\n  if (unit === \"rem\") {\n    const rootFontSize = Number.parseFloat(\n      window.getComputedStyle(document.documentElement).fontSize,\n    );\n\n    return value * (Number.isFinite(rootFontSize) ? rootFontSize : 16);\n  }\n\n  if (unit === \"vw\") {\n    return (value / 100) * window.innerWidth;\n  }\n\n  if (unit === \"vh\" || unit === \"dvh\" || unit === \"svh\" || unit === \"lvh\") {\n    return (value / 100) * window.innerHeight;\n  }\n\n  return undefined;\n}\n\nfunction resolveDrawerContentFallbackSize({\n  direction,\n  sizing,\n}: {\n  direction: DrawerDirection;\n  sizing: ReturnType<typeof resolveDrawerContentSizing>;\n}) {\n  const dimensionFallback = parseDrawerDimensionFallback(sizing.dimension);\n\n  if (dimensionFallback !== undefined) {\n    return dimensionFallback;\n  }\n\n  if (typeof window === \"undefined\") {\n    return undefined;\n  }\n\n  if (drawerDirectionAxis[direction] === \"horizontal\") {\n    return sizing.fullSize || sizing.size === \"full\"\n      ? window.innerWidth\n      : drawerHorizontalFallbackSizePx[sizing.size];\n  }\n\n  return window.innerHeight * drawerVerticalFallbackSizeRatio[sizing.size];\n}\n\nfunction syncClosedDrawerDescendantTabOrder(\n  element: HTMLElement | null,\n  closed: boolean,\n) {\n  if (!element) {\n    return;\n  }\n\n  const focusableElements = element.querySelectorAll<HTMLElement>(\n    `${drawerFocusableSelector}, [${drawerPreviousTabIndexAttribute}]`,\n  );\n\n  for (const focusableElement of focusableElements) {\n    if (closed) {\n      if (!focusableElement.hasAttribute(drawerPreviousTabIndexAttribute)) {\n        focusableElement.setAttribute(\n          drawerPreviousTabIndexAttribute,\n          focusableElement.getAttribute(\"tabindex\") ?? \"\",\n        );\n      }\n\n      focusableElement.setAttribute(\"tabindex\", \"-1\");\n      continue;\n    }\n\n    if (!focusableElement.hasAttribute(drawerPreviousTabIndexAttribute)) {\n      continue;\n    }\n\n    const previousTabIndex = focusableElement.getAttribute(\n      drawerPreviousTabIndexAttribute,\n    );\n\n    if (previousTabIndex) {\n      focusableElement.setAttribute(\"tabindex\", previousTabIndex);\n    } else {\n      focusableElement.removeAttribute(\"tabindex\");\n    }\n\n    focusableElement.removeAttribute(drawerPreviousTabIndexAttribute);\n  }\n}\n\nfunction useClosedDrawerDescendantTabOrder(\n  ref: RefObject<HTMLElement | null>,\n  closed: boolean,\n) {\n  useIsomorphicLayoutEffect(() => {\n    syncClosedDrawerDescendantTabOrder(ref.current, closed);\n  });\n}\n\nfunction renderDrawerChildren(\n  children: DrawerContentProps[\"children\"],\n  opts: DrawerCloseRenderProps,\n) {\n  return typeof children === \"function\" ? children(opts) : children;\n}\n\nfunction CloseIcon() {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={drawerCloseIconClasses}\n      fill=\"none\"\n      viewBox=\"0 0 16 16\"\n    >\n      <path\n        d=\"m4.25 4.25 7.5 7.5m0-7.5-7.5 7.5\"\n        stroke=\"currentColor\"\n        strokeLinecap=\"round\"\n        strokeWidth=\"1.75\"\n      />\n    </svg>\n  );\n}\n\nexport function drawerClassNames({\n  className,\n}: Pick<DrawerProps, \"className\"> = {}) {\n  return cn(drawerRootClasses, className);\n}\n\nexport function drawerTriggerClassNames({\n  className,\n  size = \"md\",\n  variant = \"solid\",\n}: Pick<DrawerTriggerProps, \"className\" | \"size\" | \"variant\"> = {}) {\n  return buttonClassNames({ className, size, variant });\n}\n\nexport function drawerOverlayClassNames({\n  className,\n  motionPreset = \"standard\",\n}: {\n  className?: string;\n  motionPreset?: DrawerMotionPreset;\n} = {}) {\n  return cn(\n    drawerOverlayBaseClasses,\n    getDrawerMotionPresetSettings(motionPreset).overlayDurationClass,\n    className,\n  );\n}\n\nexport function drawerContentClassNames({\n  className,\n  dimension,\n  direction = \"bottom\",\n  fullSize = false,\n  modal = true,\n  motionPreset = \"standard\",\n  open = false,\n  scrollBehavior = \"inside\",\n  size = \"md\",\n}: {\n  className?: string;\n  dimension?: DrawerDimension;\n  direction?: DrawerDirection;\n  fullSize?: boolean;\n  modal?: boolean;\n  motionPreset?: DrawerMotionPreset;\n  open?: boolean;\n  scrollBehavior?: DrawerScrollBehavior;\n  size?: DrawerSize;\n} = {}) {\n  const axis = drawerDirectionAxis[direction];\n  const resolvedSize = fullSize ? \"full\" : size;\n  const openSizeClass =\n    dimension !== undefined\n      ? drawerCustomSizeClasses[axis]\n      : axis === \"vertical\"\n        ? drawerVerticalSizeClasses[resolvedSize]\n        : drawerHorizontalSizeClasses[resolvedSize];\n  const durationClass =\n    getDrawerMotionPresetSettings(motionPreset).contentDurationClass;\n\n  if (!modal) {\n    const closedSizeClass = axis === \"vertical\" ? \"h-0\" : \"w-0\";\n\n    return cn(\n      drawerPushContainerBaseClasses,\n      durationClass,\n      drawerContentRecedeClasses,\n      drawerPushBorderClasses[direction],\n      axis === \"vertical\" ? \"w-full\" : \"h-full\",\n      open ? openSizeClass : closedSizeClass,\n      open ? \"opacity-100\" : \"opacity-0\",\n      drawerContentScrollBehaviorClasses[scrollBehavior],\n      className,\n    );\n  }\n\n  return cn(\n    drawerContentBaseClasses,\n    durationClass,\n    drawerContentRecedeClasses,\n    drawerContentPositionClasses[direction],\n    openSizeClass,\n    drawerContentScrollBehaviorClasses[scrollBehavior],\n    className,\n  );\n}\n\nexport function drawerHeaderClassNames({\n  className,\n  hasCloseButton = false,\n}: Pick<DrawerHeaderProps, \"className\"> & {\n  hasCloseButton?: boolean;\n} = {}) {\n  return cn(\n    drawerHeaderClasses,\n    hasCloseButton && drawerHeaderWithCloseButtonClasses,\n    className,\n  );\n}\n\nexport function drawerFooterClassNames({\n  className,\n}: Pick<DrawerFooterProps, \"className\"> = {}) {\n  return cn(drawerFooterClasses, className);\n}\n\nexport function drawerTitleClassNames({\n  className,\n  visuallyHidden = false,\n}: Pick<DrawerTitleProps, \"className\" | \"visuallyHidden\"> = {}) {\n  return cn(\n    drawerTitleClasses,\n    visuallyHidden && visuallyHiddenClasses,\n    className,\n  );\n}\n\nexport function drawerDescriptionClassNames({\n  className,\n}: Pick<DrawerDescriptionProps, \"className\"> = {}) {\n  return cn(drawerDescriptionClasses, className);\n}\n\nexport function drawerCloseButtonClassNames({\n  className,\n}: {\n  className?: string;\n} = {}) {\n  return cn(drawerCloseButtonClasses, className);\n}\n\nexport const Drawer = forwardRef<HTMLDivElement, DrawerProps>(\n  (\n    {\n      \"data-slot\": dataSlot,\n      activeSnapPoint,\n      backgroundScale = false,\n      children,\n      className,\n      closeThreshold = DRAWER_DEFAULT_CLOSE_THRESHOLD,\n      defaultOpen,\n      defaultSnapPoint,\n      dimension,\n      direction = \"bottom\",\n      dragHandleOnly = true,\n      edgeSwipeHitRegionSize = DRAWER_DEFAULT_EDGE_SWIPE_HIT_REGION_SIZE,\n      edgeSwipeToOpen = false,\n      fullSize = false,\n      modal = true,\n      motionPreset = \"standard\",\n      onActiveSnapPointChange,\n      onOpenChange,\n      open,\n      reducedMotion,\n      size = \"md\",\n      snapPoints,\n      velocityThreshold = DRAWER_DEFAULT_VELOCITY_THRESHOLD,\n    },\n    ref,\n  ) => {\n    const [uncontrolledOpen, setUncontrolledOpen] = useState(\n      defaultOpen ?? false,\n    );\n    const isControlled = open !== undefined;\n    const resolvedOpen = open ?? uncontrolledOpen;\n    const previousOpenRef = useRef(resolvedOpen);\n    const triggerElementRef = useRef<HTMLButtonElement | null>(null);\n    const prefersReducedMotion = useDrawerReducedMotion();\n    const resolvedReducedMotion = reducedMotion ?? prefersReducedMotion;\n    const { portalContainer: ownPortalContainer, rootRef } =\n      useProviderPortalRoot<HTMLDivElement>({\n        forwardedRef: ref,\n        portalSlot: \"drawer-portal-container\",\n      });\n    const nestedParentContext = useContext(DrawerNestedContext);\n    const portalContainer =\n      nestedParentContext?.portalContainer ?? ownPortalContainer;\n\n    useEffect(() => {\n      if (!nestedParentContext || !resolvedOpen) {\n        return undefined;\n      }\n\n      nestedParentContext.registerChildOpen();\n\n      return () => {\n        nestedParentContext.unregisterChildOpen();\n      };\n    }, [nestedParentContext, resolvedOpen]);\n\n    const handleOpenChange = (isOpen: boolean) => {\n      if (!isControlled) {\n        setUncontrolledOpen(isOpen);\n      }\n\n      onOpenChange?.(isOpen);\n    };\n\n    useEffect(() => {\n      const wasOpen = previousOpenRef.current;\n\n      previousOpenRef.current = resolvedOpen;\n\n      if (wasOpen && !resolvedOpen && typeof window !== \"undefined\") {\n        const restoreFocus = window.setTimeout(() => {\n          triggerElementRef.current?.focus();\n        }, 0);\n\n        return () => {\n          window.clearTimeout(restoreFocus);\n        };\n      }\n\n      return undefined;\n    }, [resolvedOpen]);\n\n    const rootContextValue = useMemo<DrawerRootContextValue>(\n      () => ({\n        nested: Boolean(nestedParentContext),\n        activeSnapPoint,\n        backgroundScale,\n        closeThreshold,\n        defaultSnapPoint,\n        dimension,\n        direction,\n        dragHandleOnly,\n        fullSize,\n        modal,\n        motionPreset,\n        onActiveSnapPointChange,\n        onOpenChange: handleOpenChange,\n        open: resolvedOpen,\n        portalContainer,\n        reducedMotion,\n        setTriggerElement: (element) => {\n          triggerElementRef.current = element;\n        },\n        size,\n        snapPoints,\n        velocityThreshold,\n      }),\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      [\n        nestedParentContext,\n        activeSnapPoint,\n        backgroundScale,\n        closeThreshold,\n        defaultSnapPoint,\n        dimension,\n        direction,\n        dragHandleOnly,\n        fullSize,\n        modal,\n        motionPreset,\n        onActiveSnapPointChange,\n        portalContainer,\n        reducedMotion,\n        size,\n        resolvedOpen,\n        isControlled,\n        snapPoints,\n        velocityThreshold,\n      ],\n    );\n    const showEdgeSwipeZone =\n      edgeSwipeToOpen &&\n      shouldEnableDrawerMotion({\n        motionPreset,\n        reducedMotion: resolvedReducedMotion,\n      }) &&\n      !resolvedOpen;\n\n    return (\n      <div\n        ref={rootRef}\n        data-direction={direction}\n        data-modal={modal ? \"true\" : \"false\"}\n        data-slot={dataSlot ?? \"drawer\"}\n        className={drawerClassNames({ className })}\n      >\n        <DethinkPortalProvider container={portalContainer}>\n          <DrawerRootContext.Provider value={rootContextValue}>\n            {children}\n          </DrawerRootContext.Provider>\n        </DethinkPortalProvider>\n        {showEdgeSwipeZone ? (\n          <DrawerEdgeSwipeZone\n            direction={direction}\n            hitRegionSize={edgeSwipeHitRegionSize}\n            onOpenChange={handleOpenChange}\n            velocityThreshold={velocityThreshold}\n          />\n        ) : null}\n      </div>\n    );\n  },\n);\n\nDrawer.displayName = \"Drawer\";\n\nexport const DrawerTrigger = forwardRef<HTMLButtonElement, DrawerTriggerProps>(\n  (\n    {\n      \"data-slot\": dataSlot,\n      children,\n      className,\n      onPress,\n      size = \"md\",\n      variant = \"solid\",\n      ...props\n    },\n    ref,\n  ) => {\n    const rootContext = useContext(DrawerRootContext);\n    const setTriggerRef = (node: HTMLButtonElement | null) => {\n      rootContext?.setTriggerElement(node);\n    };\n\n    return (\n      <AriaButton\n        {...props}\n        ref={composeRefs(ref, setTriggerRef)}\n        aria-expanded={rootContext?.open ?? false}\n        aria-haspopup={rootContext?.modal ? \"dialog\" : undefined}\n        data-direction={rootContext?.direction}\n        data-modal={\n          rootContext ? (rootContext.modal ? \"true\" : \"false\") : undefined\n        }\n        data-slot={dataSlot ?? \"drawer-trigger\"}\n        className={drawerTriggerClassNames({ className, size, variant })}\n        onPress={(event) => {\n          rootContext?.onOpenChange(true);\n          onPress?.(event);\n        }}\n      >\n        {children}\n      </AriaButton>\n    );\n  },\n);\n\nDrawerTrigger.displayName = \"DrawerTrigger\";\n\nfunction DrawerPushContent(\n  {\n    \"aria-describedby\": ariaDescribedBy,\n    \"aria-label\": ariaLabel,\n    \"aria-labelledby\": ariaLabelledBy,\n    children,\n    className,\n    closeButtonClassName,\n    closeButtonLabel,\n    dimension,\n    fullSize,\n    keyboardDismissDisabled,\n    layoutId,\n    scrollBehavior = \"inside\",\n    showCloseButton,\n    size,\n    style,\n    ...props\n  }: DrawerContentProps,\n  ref: Ref<HTMLDivElement>,\n) {\n  const rootContext = useContext(DrawerRootContext);\n  const direction = rootContext?.direction ?? \"bottom\";\n  const open = rootContext?.open ?? false;\n  const motionPreset = rootContext?.motionPreset ?? \"standard\";\n  const outerRef = useRef<HTMLDivElement | null>(null);\n  useClosedDrawerDescendantTabOrder(outerRef, !open);\n  const prefersReducedMotion = useDrawerReducedMotion();\n  const resolvedReducedMotion =\n    rootContext?.reducedMotion ?? prefersReducedMotion;\n  const motionEnabled = shouldEnableDrawerMotion({\n    motionPreset,\n    reducedMotion: resolvedReducedMotion,\n  });\n  const [openChildCount, setOpenChildCount] = useState(0);\n  const receded = openChildCount > 0;\n  const nestedContextValue = useMemo<DrawerNestedContextValue>(\n    () => ({\n      portalContainer: rootContext?.portalContainer ?? null,\n      registerChildOpen: () => setOpenChildCount((count) => count + 1),\n      unregisterChildOpen: () =>\n        setOpenChildCount((count) => Math.max(0, count - 1)),\n    }),\n    [rootContext?.portalContainer],\n  );\n  const sizing = resolveDrawerContentSizing({\n    dimension,\n    fullSize,\n    rootContext,\n    size,\n  });\n  const fallbackContentSize = resolveDrawerContentFallbackSize({\n    direction,\n    sizing,\n  });\n  const { dragControls, getMotionProps } = useDrawerDrag({\n    activeSnapPoint: rootContext?.activeSnapPoint,\n    closeThreshold:\n      rootContext?.closeThreshold ?? DRAWER_DEFAULT_CLOSE_THRESHOLD,\n    contentRef: outerRef,\n    defaultSnapPoint: rootContext?.defaultSnapPoint,\n    direction,\n    fallbackContentSize,\n    motionPreset,\n    onActiveSnapPointChange: rootContext?.onActiveSnapPointChange,\n    onOpenChange: (nextOpen) => rootContext?.onOpenChange(nextOpen),\n    open,\n    receded,\n    snapPoints: rootContext?.snapPoints,\n    velocityThreshold:\n      rootContext?.velocityThreshold ?? DRAWER_DEFAULT_VELOCITY_THRESHOLD,\n  });\n  const defaultTitleId = useId();\n  const [titleId, setTitleId] = useState<string | null>(null);\n  const [descriptionId, setDescriptionId] = useState<string | null>(null);\n  const labelledBy =\n    ariaLabelledBy ?? (ariaLabel ? undefined : (titleId ?? defaultTitleId));\n  const panelRef = useRef<HTMLDivElement | null>(null);\n  const wasOpenRef = useRef(open);\n  const contextValue = useMemo(\n    () => ({\n      defaultTitleId,\n      dragControls: motionEnabled ? dragControls : null,\n      hasCloseButton: Boolean(showCloseButton),\n      motionEnabled,\n      setDescriptionId,\n      setTitleId,\n    }),\n    [defaultTitleId, dragControls, motionEnabled, showCloseButton],\n  );\n\n  useEffect(() => {\n    const wasOpen = wasOpenRef.current;\n\n    wasOpenRef.current = open;\n\n    if (\n      !wasOpen &&\n      open &&\n      panelRef.current &&\n      !panelRef.current.contains(document.activeElement)\n    ) {\n      panelRef.current.focus();\n    }\n  }, [open]);\n\n  function handleKeyDown(event: ReactKeyboardEvent<HTMLDivElement>) {\n    if (!keyboardDismissDisabled && event.key === \"Escape\") {\n      event.stopPropagation();\n      rootContext?.onOpenChange(false);\n    }\n  }\n\n  const close = () => {\n    rootContext?.onOpenChange(false);\n  };\n  const contentStyle = drawerContentStyle({\n    dimension: sizing.dimension,\n    style,\n  });\n\n  const contentClassName = drawerContentClassNames({\n    className,\n    dimension: sizing.dimension,\n    direction,\n    fullSize: sizing.fullSize,\n    modal: false,\n    motionPreset,\n    open,\n    scrollBehavior,\n    size: sizing.size,\n  });\n\n  const hasVisibleSnapLayout =\n    motionEnabled && Boolean(rootContext?.snapPoints?.length);\n  const snapPanelClasses = hasVisibleSnapLayout\n    ? drawerSnapPanelClassNames(direction)\n    : undefined;\n\n  const panel = (\n    // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- The dialog handles bubbled Escape keys while focus remains on its controls.\n    <div\n      ref={panelRef}\n      aria-describedby={joinIds(ariaDescribedBy, descriptionId ?? undefined)}\n      aria-hidden={open ? undefined : true}\n      aria-label={ariaLabel}\n      aria-labelledby={labelledBy}\n      aria-modal=\"false\"\n      className={cn(drawerPanelClasses, snapPanelClasses)}\n      data-slot=\"drawer-panel\"\n      onKeyDown={handleKeyDown}\n      role=\"dialog\"\n      tabIndex={-1}\n    >\n      <DrawerContentContext.Provider value={contextValue}>\n        <DrawerNestedContext.Provider value={nestedContextValue}>\n          {showCloseButton ? (\n            <DrawerClose\n              aria-label={closeButtonLabel ?? \"Close drawer\"}\n              className={drawerCloseButtonClassNames({\n                className: closeButtonClassName,\n              })}\n            />\n          ) : null}\n          {renderDrawerChildren(children, { close })}\n        </DrawerNestedContext.Provider>\n      </DrawerContentContext.Provider>\n    </div>\n  );\n\n  if (!motionEnabled) {\n    return (\n      <div\n        {...props}\n        ref={(node) => {\n          composeRefs(ref, outerRef)(node);\n        }}\n        data-direction={direction}\n        data-drawer-receded={receded ? \"true\" : undefined}\n        data-modal=\"false\"\n        data-motion={motionPreset}\n        data-motion-direction={direction}\n        data-reduced-motion={resolvedReducedMotion ? \"true\" : undefined}\n        data-slot=\"drawer-content\"\n        data-state={open ? \"open\" : \"closed\"}\n        inert={!open ? true : undefined}\n        style={contentStyle}\n        className={contentClassName}\n      >\n        {panel}\n      </div>\n    );\n  }\n\n  const motionProps = getMotionProps(rootContext?.dragHandleOnly ?? true);\n  const motionStyle = {\n    ...contentStyle,\n    ...motionProps.style,\n  };\n\n  return (\n    <MotionDiv\n      {...props}\n      {...motionProps}\n      layoutId={layoutId}\n      ref={(node) => {\n        composeRefs(ref, outerRef)(node);\n      }}\n      data-direction={direction}\n      data-drawer-receded={receded ? \"true\" : undefined}\n      data-modal=\"false\"\n      data-motion={motionPreset}\n      data-motion-direction={direction}\n      data-reduced-motion={resolvedReducedMotion ? \"true\" : undefined}\n      data-slot=\"drawer-content\"\n      data-state={open ? \"open\" : \"closed\"}\n      inert={!open ? true : undefined}\n      style={motionStyle}\n      className={contentClassName}\n    >\n      {panel}\n    </MotionDiv>\n  );\n}\n\nconst ForwardedDrawerPushContent = forwardRef(DrawerPushContent);\n\nfunction DrawerModalContent(\n  {\n    \"aria-describedby\": ariaDescribedBy,\n    \"aria-label\": ariaLabel,\n    \"aria-labelledby\": ariaLabelledBy,\n    children,\n    className,\n    closeButtonClassName,\n    closeButtonLabel,\n    dimension,\n    dismissible = false,\n    fullSize,\n    keyboardDismissDisabled = false,\n    layoutId,\n    overlayClassName,\n    scrollBehavior = \"inside\",\n    shouldCloseOnInteractOutside,\n    showCloseButton,\n    size,\n    style,\n    ...props\n  }: DrawerContentProps,\n  ref: Ref<HTMLDivElement>,\n) {\n  const rootContext = useContext(DrawerRootContext);\n  const direction = rootContext?.direction ?? \"bottom\";\n  const open = rootContext?.open ?? false;\n  const motionPreset = rootContext?.motionPreset ?? \"standard\";\n  const outerRef = useRef<HTMLDivElement | null>(null);\n  const prefersReducedMotion = useDrawerReducedMotion();\n  const resolvedReducedMotion =\n    rootContext?.reducedMotion ?? prefersReducedMotion;\n  const motionEnabled = shouldEnableDrawerMotion({\n    motionPreset,\n    reducedMotion: resolvedReducedMotion,\n  });\n  const [openChildCount, setOpenChildCount] = useState(0);\n  const receded = openChildCount > 0;\n  const nestedContextValue = useMemo<DrawerNestedContextValue>(\n    () => ({\n      portalContainer: rootContext?.portalContainer ?? null,\n      registerChildOpen: () => setOpenChildCount((count) => count + 1),\n      unregisterChildOpen: () =>\n        setOpenChildCount((count) => Math.max(0, count - 1)),\n    }),\n    [rootContext?.portalContainer],\n  );\n  const sizing = resolveDrawerContentSizing({\n    dimension,\n    fullSize,\n    rootContext,\n    size,\n  });\n  const fallbackContentSize = resolveDrawerContentFallbackSize({\n    direction,\n    sizing,\n  });\n  const { dragControls, getMotionProps, overlayOpacity } = useDrawerDrag({\n    activeSnapPoint: rootContext?.activeSnapPoint,\n    closeThreshold:\n      rootContext?.closeThreshold ?? DRAWER_DEFAULT_CLOSE_THRESHOLD,\n    contentRef: outerRef,\n    defaultSnapPoint: rootContext?.defaultSnapPoint,\n    direction,\n    fallbackContentSize,\n    motionPreset,\n    onActiveSnapPointChange: rootContext?.onActiveSnapPointChange,\n    onOpenChange: (nextOpen) => rootContext?.onOpenChange(nextOpen),\n    open,\n    receded,\n    snapPoints: rootContext?.snapPoints,\n    velocityThreshold:\n      rootContext?.velocityThreshold ?? DRAWER_DEFAULT_VELOCITY_THRESHOLD,\n  });\n  const defaultTitleId = useId();\n  const [titleId, setTitleId] = useState<string | null>(null);\n  const [descriptionId, setDescriptionId] = useState<string | null>(null);\n  const labelledBy =\n    ariaLabelledBy ?? (ariaLabel ? undefined : (titleId ?? defaultTitleId));\n  const contextValue = useMemo(\n    () => ({\n      defaultTitleId,\n      dragControls: motionEnabled ? dragControls : null,\n      hasCloseButton: Boolean(showCloseButton),\n      motionEnabled,\n      setDescriptionId,\n      setTitleId,\n    }),\n    [defaultTitleId, dragControls, motionEnabled, showCloseButton],\n  );\n\n  useDrawerBackgroundScale({\n    active: Boolean(rootContext?.backgroundScale) && open,\n    reducedMotion: !motionEnabled,\n  });\n  const contentStyle = drawerContentStyle({\n    dimension: sizing.dimension,\n    style,\n  });\n\n  const contentClassName = drawerContentClassNames({\n    className,\n    dimension: sizing.dimension,\n    direction,\n    fullSize: sizing.fullSize,\n    modal: true,\n    motionPreset,\n    scrollBehavior,\n    size: sizing.size,\n  });\n\n  const panel = (\n    <AriaDialog\n      aria-describedby={joinIds(ariaDescribedBy, descriptionId ?? undefined)}\n      aria-label={ariaLabel}\n      aria-labelledby={labelledBy}\n      className={cn(\n        drawerPanelClasses,\n        motionEnabled &&\n          rootContext?.snapPoints?.length &&\n          drawerSnapPanelClassNames(direction),\n      )}\n      data-slot=\"drawer-panel\"\n      role=\"dialog\"\n    >\n      {(opts: AriaDialogRenderProps) => (\n        <DrawerContentContext.Provider value={contextValue}>\n          <DrawerNestedContext.Provider value={nestedContextValue}>\n            {showCloseButton ? (\n              <DrawerClose\n                aria-label={closeButtonLabel ?? \"Close drawer\"}\n                className={drawerCloseButtonClassNames({\n                  className: closeButtonClassName,\n                })}\n              />\n            ) : null}\n            {renderDrawerChildren(children, { close: opts.close })}\n          </DrawerNestedContext.Provider>\n        </DrawerContentContext.Provider>\n      )}\n    </AriaDialog>\n  );\n\n  return (\n    <ModalOverlay\n      isDismissable={dismissible}\n      isKeyboardDismissDisabled={keyboardDismissDisabled}\n      isOpen={open}\n      shouldCloseOnInteractOutside={shouldCloseOnInteractOutside}\n      onOpenChange={(isOpen) => rootContext?.onOpenChange(isOpen)}\n      data-direction={direction}\n      data-slot=\"drawer-overlay\"\n      data-nested={rootContext?.nested ? \"true\" : undefined}\n      style={motionEnabled ? { backgroundColor: \"transparent\" } : undefined}\n      className={drawerOverlayClassNames({\n        className: overlayClassName,\n        motionPreset,\n      })}\n    >\n      {motionEnabled && !rootContext?.nested ? (\n        <MotionDiv\n          aria-hidden=\"true\"\n          data-slot=\"drawer-scrim\"\n          className=\"pointer-events-none absolute inset-0 bg-[color-mix(in_srgb,var(--dt-overlay-scrim)_75%,transparent)]\"\n          style={{ opacity: overlayOpacity }}\n        />\n      ) : null}\n      {motionEnabled ? (\n        (() => {\n          const motionProps = getMotionProps(\n            rootContext?.dragHandleOnly ?? true,\n          );\n          const motionStyle = {\n            ...contentStyle,\n            ...motionProps.style,\n          };\n\n          return (\n            <MotionModal\n              {...props}\n              {...motionProps}\n              layoutId={layoutId}\n              ref={(node: HTMLDivElement | null) => {\n                // eslint-disable-next-line react-hooks/refs -- This callback assigns refs at commit; the surrounding render IIFE confuses compiler analysis.\n                composeRefs(ref, outerRef)(node);\n              }}\n              data-direction={direction}\n              data-drawer-receded={receded ? \"true\" : undefined}\n              data-modal=\"true\"\n              data-motion={motionPreset}\n              data-motion-direction={direction}\n              data-reduced-motion={resolvedReducedMotion ? \"true\" : undefined}\n              data-slot=\"drawer-content\"\n              style={motionStyle}\n              className={contentClassName}\n            >\n              {panel}\n            </MotionModal>\n          );\n        })()\n      ) : (\n        <Modal\n          {...props}\n          ref={(node) => {\n            composeRefs(ref, outerRef)(node);\n          }}\n          data-direction={direction}\n          data-drawer-receded={receded ? \"true\" : undefined}\n          data-modal=\"true\"\n          data-motion={motionPreset}\n          data-motion-direction={direction}\n          data-reduced-motion={resolvedReducedMotion ? \"true\" : undefined}\n          data-slot=\"drawer-content\"\n          style={contentStyle}\n          className={contentClassName}\n        >\n          {panel}\n        </Modal>\n      )}\n    </ModalOverlay>\n  );\n}\n\nconst ForwardedDrawerModalContent = forwardRef(DrawerModalContent);\n\nexport const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(\n  (props, ref) => {\n    const rootContext = useContext(DrawerRootContext);\n    const modal = rootContext?.modal ?? true;\n\n    if (!modal) {\n      return <ForwardedDrawerPushContent {...props} ref={ref} />;\n    }\n\n    return <ForwardedDrawerModalContent {...props} ref={ref} />;\n  },\n);\n\nDrawerContent.displayName = \"DrawerContent\";\n\nexport const DrawerHeader = forwardRef<HTMLDivElement, DrawerHeaderProps>(\n  ({ className, ...props }, ref) => {\n    const context = useContext(DrawerContentContext);\n\n    return (\n      <div\n        {...props}\n        ref={ref}\n        data-slot=\"drawer-header\"\n        className={drawerHeaderClassNames({\n          className,\n          hasCloseButton: context?.hasCloseButton,\n        })}\n      />\n    );\n  },\n);\n\nDrawerHeader.displayName = \"DrawerHeader\";\n\nexport const DrawerFooter = forwardRef<HTMLDivElement, DrawerFooterProps>(\n  ({ className, ...props }, ref) => (\n    <div\n      {...props}\n      ref={ref}\n      data-slot=\"drawer-footer\"\n      className={drawerFooterClassNames({ className })}\n    />\n  ),\n);\n\nDrawerFooter.displayName = \"DrawerFooter\";\n\nexport const DrawerTitle = forwardRef<HTMLHeadingElement, DrawerTitleProps>(\n  ({ className, id, level = 2, visuallyHidden = false, ...props }, ref) => {\n    const context = useContext(DrawerContentContext);\n    const generatedId = useId();\n    const resolvedId = id ?? context?.defaultTitleId ?? generatedId;\n    const HeadingTag = `h${level}` as const;\n\n    useIsomorphicLayoutEffect(() => {\n      context?.setTitleId(resolvedId);\n\n      return () => {\n        context?.setTitleId(null);\n      };\n    }, [context, resolvedId]);\n\n    return (\n      <HeadingTag\n        {...props}\n        ref={ref}\n        id={resolvedId}\n        data-slot=\"drawer-title\"\n        className={drawerTitleClassNames({ className, visuallyHidden })}\n      />\n    );\n  },\n);\n\nDrawerTitle.displayName = \"DrawerTitle\";\n\nexport const DrawerDescription = forwardRef<\n  HTMLParagraphElement,\n  DrawerDescriptionProps\n>(({ className, id, ...props }, ref) => {\n  const context = useContext(DrawerContentContext);\n  const generatedId = useId();\n  const resolvedId = id ?? generatedId;\n\n  useEffect(() => {\n    context?.setDescriptionId(resolvedId);\n\n    return () => {\n      context?.setDescriptionId(null);\n    };\n  }, [context, resolvedId]);\n\n  return (\n    <p\n      {...props}\n      ref={ref}\n      id={resolvedId}\n      data-slot=\"drawer-description\"\n      className={drawerDescriptionClassNames({ className })}\n    />\n  );\n});\n\nDrawerDescription.displayName = \"DrawerDescription\";\n\nexport const DrawerClose = forwardRef<HTMLButtonElement, DrawerCloseProps>(\n  (\n    {\n      \"aria-label\": ariaLabel,\n      children,\n      className,\n      onPress,\n      size,\n      variant = \"ghost\",\n      ...props\n    },\n    ref,\n  ) => {\n    const rootContext = useContext(DrawerRootContext);\n    const hasVisibleChildren = children != null;\n    const resolvedSize = size ?? (hasVisibleChildren ? \"md\" : \"icon\");\n\n    return (\n      <AriaButton\n        {...props}\n        ref={ref}\n        aria-label={\n          ariaLabel ?? (hasVisibleChildren ? undefined : \"Close drawer\")\n        }\n        data-slot=\"drawer-close\"\n        className={drawerTriggerClassNames({\n          className,\n          size: resolvedSize,\n          variant,\n        })}\n        onPress={(event) => {\n          rootContext?.onOpenChange(false);\n          onPress?.(event);\n        }}\n      >\n        {hasVisibleChildren ? children : <CloseIcon />}\n      </AriaButton>\n    );\n  },\n);\n\nDrawerClose.displayName = \"DrawerClose\";\n\nexport const DrawerHandle = forwardRef<HTMLDivElement, DrawerHandleProps>(\n  ({ className, ...props }, ref) => {\n    const rootContext = useContext(DrawerRootContext);\n    const contentContext = useContext(DrawerContentContext);\n    const direction = rootContext?.direction ?? \"bottom\";\n\n    if (contentContext?.motionEnabled && contentContext.dragControls) {\n      return (\n        <DrawerMotionHandle\n          {...props}\n          ref={ref}\n          className={className}\n          direction={direction}\n          dragControls={contentContext.dragControls}\n        />\n      );\n    }\n\n    return (\n      <div\n        {...props}\n        ref={ref}\n        aria-hidden=\"true\"\n        data-direction={direction}\n        data-slot=\"drawer-handle\"\n        className={drawerHandleClassNames({ className })}\n      />\n    );\n  },\n);\n\nDrawerHandle.displayName = \"DrawerHandle\";\n"
    },
    {
      "path": "packages/components/src/components/drawer/drawer-motion.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/drawer-motion.tsx",
      "content": "import { Modal } from \"react-aria-components\";\nimport {\n  animate,\n  motion,\n  useDragControls,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type DragControls,\n  type MotionValue,\n  type PanInfo,\n  type Transition,\n} from \"motion/react\";\nimport {\n  forwardRef,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentType,\n  type RefObject,\n} from \"react\";\nimport {\n  clampSnapPointFraction,\n  drawerDragAxis,\n  drawerDragClosingSign,\n  findNearestDrawerSnapStop,\n  resolveDrawerActiveSnapStop,\n  resolveDrawerDragReleaseStop,\n  resolveDrawerSnapStops,\n  shouldDismissDrawerFromVelocity,\n  type DrawerSnapPoint,\n} from \"./drawer-snap\";\nimport type { DrawerDirection, DrawerMotionPreset } from \"./drawer-types\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Only this module imports `motion/react`. The DOM layer in drawer.tsx\n * takes a `motionEnabled` boolean and falls back to plain elements with no\n * drag/spring behavior, matching SlotPlanner's motion-isolation convention.\n */\n\nexport type { DragControls } from \"motion/react\";\n\n/**\n * Explicitly typed (rather than inferred) so the package's declaration\n * output does not need to name react-aria-components' internal,\n * non-exported prop-helper types. Prop safety for the drag/motion props\n * spread onto this component is enforced at the `getMotionProps` call\n * site instead.\n */\nexport const MotionModal: ComponentType<Record<string, unknown>> =\n  motion.create(Modal);\nexport const MotionDiv = motion.div;\n\ninterface DrawerMotionPresetSettings {\n  contentDurationClass: string;\n  overlayDurationClass: string;\n  recedeScale: number;\n  springTransition: Transition;\n}\n\n/**\n * The single source of spring/duration tuning per `motionPreset`. `drag`,\n * snap-point settling, and nested-drawer recede all `animate()` through\n * `springTransition` from this table — \"reuse the same spring primitives\"\n * means literally the same transition object, not merely a similar feel.\n * `standard` matches the fixed spring this component shipped with before\n * motion presets existed, so the default look is unchanged.\n */\nconst drawerMotionPresetSettings: Record<\n  DrawerMotionPreset,\n  DrawerMotionPresetSettings\n> = {\n  none: {\n    contentDurationClass: \"motion-safe:duration-0\",\n    overlayDurationClass: \"motion-safe:duration-0\",\n    recedeScale: 1,\n    springTransition: { duration: 0 },\n  },\n  subtle: {\n    contentDurationClass: \"motion-safe:duration-[180ms]\",\n    overlayDurationClass: \"motion-safe:duration-150\",\n    recedeScale: 0.98,\n    springTransition: {\n      damping: 36,\n      mass: 0.82,\n      stiffness: 310,\n      type: \"spring\",\n    },\n  },\n  standard: {\n    contentDurationClass: \"motion-safe:duration-[240ms]\",\n    overlayDurationClass: \"motion-safe:duration-150\",\n    recedeScale: 0.96,\n    springTransition: {\n      damping: 30,\n      mass: 0.92,\n      stiffness: 360,\n      type: \"spring\",\n    },\n  },\n  expressive: {\n    contentDurationClass: \"motion-safe:duration-[320ms]\",\n    overlayDurationClass: \"motion-safe:duration-200\",\n    recedeScale: 0.92,\n    springTransition: {\n      damping: 23,\n      mass: 0.96,\n      stiffness: 420,\n      type: \"spring\",\n    },\n  },\n};\n\nexport function getDrawerMotionPresetSettings(\n  motionPreset: DrawerMotionPreset,\n): DrawerMotionPresetSettings {\n  return drawerMotionPresetSettings[motionPreset];\n}\n\nexport function shouldEnableDrawerMotion({\n  motionPreset,\n  reducedMotion,\n}: {\n  motionPreset: DrawerMotionPreset;\n  reducedMotion: boolean;\n}): boolean {\n  return motionPreset !== \"none\" && !reducedMotion;\n}\n\nconst DRAWER_DRAG_ELASTIC = 0.12;\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nexport function useDrawerReducedMotion(): boolean {\n  return useReducedMotion() === true;\n}\n\nexport interface UseDrawerDragOptions {\n  activeSnapPoint?: number;\n  closeThreshold: number;\n  contentRef: RefObject<HTMLElement | null>;\n  defaultSnapPoint?: number;\n  direction: DrawerDirection;\n  fallbackContentSize?: number;\n  motionPreset: DrawerMotionPreset;\n  onActiveSnapPointChange?: (snapPoint: number) => void;\n  onOpenChange: (open: boolean) => void;\n  open: boolean;\n  receded?: boolean;\n  snapPoints?: DrawerSnapPoint[];\n  velocityThreshold: number;\n}\n\nexport interface DrawerDragMotionProps {\n  drag: \"x\" | \"y\" | false;\n  dragControls: DragControls;\n  dragConstraints: Partial<Record<\"top\" | \"right\" | \"bottom\" | \"left\", number>>;\n  dragElastic: number;\n  dragListener: boolean;\n  dragMomentum: false;\n  onDragEnd: (event: PointerEvent, info: PanInfo) => void;\n  style: ({ x: number; scale: number } | { y: number; scale: number }) & {\n    \"--drawer-visible-size\"?: MotionValue<string>;\n  };\n}\n\nexport interface UseDrawerDragResult {\n  activeSnapPoint: number;\n  dragControls: DragControls;\n  getMotionProps: (dragHandleOnly: boolean) => DrawerDragMotionProps;\n  hasSnapPoints: boolean;\n  overlayOpacity: MotionValue<number>;\n}\n\nfunction measureContentSize(\n  contentRef: RefObject<HTMLElement | null>,\n  axis: \"x\" | \"y\",\n  fallbackContentSize?: number,\n): number {\n  const element = contentRef.current;\n\n  if (!element) {\n    return (\n      fallbackContentSize ??\n      (typeof window === \"undefined\"\n        ? 0\n        : axis === \"x\"\n          ? window.innerWidth\n          : window.innerHeight)\n    );\n  }\n\n  // Measure layout size, not the scaled size of a receded parent.\n  const measuredSize =\n    axis === \"x\" ? element.offsetWidth : element.offsetHeight;\n\n  if (measuredSize > 0 || typeof window === \"undefined\") {\n    return measuredSize;\n  }\n\n  return (\n    fallbackContentSize ??\n    (axis === \"x\" ? window.innerWidth : window.innerHeight)\n  );\n}\n\n/**\n * Wires spring-driven drag-to-dismiss and snap-point settling onto whatever\n * element the caller renders with the returned motion props (a\n * `MotionModal` in modal mode, a `motion.div` in push mode). The drag\n * mechanic is a translate offset layered on top of the element's existing\n * static size/position — dragging never changes the box's actual\n * width/height, only how far it is shifted toward its closed edge.\n */\nexport function useDrawerDrag({\n  activeSnapPoint,\n  closeThreshold,\n  contentRef,\n  defaultSnapPoint,\n  direction,\n  fallbackContentSize,\n  motionPreset,\n  onActiveSnapPointChange,\n  onOpenChange,\n  open,\n  receded = false,\n  snapPoints,\n  velocityThreshold,\n}: UseDrawerDragOptions): UseDrawerDragResult {\n  const axis = drawerDragAxis(direction);\n  const closingSign = drawerDragClosingSign(direction);\n  const hasSnapPoints = Boolean(snapPoints && snapPoints.length > 0);\n  const stops = useMemo(() => resolveDrawerSnapStops(snapPoints), [snapPoints]);\n  const openStops = useMemo(() => stops.filter((stop) => stop > 0), [stops]);\n  const isControlled = activeSnapPoint !== undefined;\n  const [uncontrolledSnapPoint, setUncontrolledSnapPoint] = useState(() =>\n    resolveDrawerActiveSnapStop({ activeSnapPoint, defaultSnapPoint, stops }),\n  );\n  const resolvedSnapPoint = isControlled\n    ? findNearestDrawerSnapStop(\n        clampSnapPointFraction(activeSnapPoint!),\n        openStops,\n      )\n    : uncontrolledSnapPoint;\n  const { recedeScale, springTransition } =\n    getDrawerMotionPresetSettings(motionPreset);\n\n  const translateMotionValue = useMotionValue(0);\n  const scaleMotionValue = useMotionValue(1);\n  const contentSizeValue = useMotionValue(fallbackContentSize ?? 0);\n  const recedeOffset = useMotionValue(0);\n  const recedeTranslate = useTransform(recedeOffset, (value) =>\n    axis === \"x\" ? `${value}px 0px` : `0px ${value}px`,\n  );\n  const visibleSize = useTransform(\n    () =>\n      `${Math.max(0, contentSizeValue.get() - Math.max(0, translateMotionValue.get() * closingSign))}px`,\n  );\n  const overlayOpacity = useTransform(() => {\n    const restingVisibleSize = contentSizeValue.get() * resolvedSnapPoint;\n    if (restingVisibleSize <= 0) return 0;\n    return Math.min(\n      1,\n      Math.max(\n        0,\n        (contentSizeValue.get() - translateMotionValue.get() * closingSign) /\n          restingVisibleSize,\n      ),\n    );\n  });\n  const dragControls = useDragControls();\n  const startSnapPointRef = useRef(resolvedSnapPoint);\n  const hasMountedRef = useRef(false);\n  const wasOpenRef = useRef(open);\n\n  useIsomorphicLayoutEffect(() => {\n    const wasOpen = wasOpenRef.current;\n\n    wasOpenRef.current = open;\n    startSnapPointRef.current = resolvedSnapPoint;\n\n    const contentSize = measureContentSize(\n      contentRef,\n      axis,\n      fallbackContentSize,\n    );\n    if (contentSize <= 0) {\n      return undefined;\n    }\n    contentSizeValue.set(contentSize);\n\n    const openTarget = (1 - resolvedSnapPoint) * contentSize * closingSign;\n    const closedTarget = contentSize * closingSign;\n    const target = open ? openTarget : closedTarget;\n\n    if (!hasMountedRef.current) {\n      hasMountedRef.current = true;\n      translateMotionValue.set(open ? closedTarget : target);\n\n      if (!open) {\n        return undefined;\n      }\n    } else if (open && !wasOpen) {\n      translateMotionValue.set(closedTarget);\n    } else if (!open && !wasOpen) {\n      translateMotionValue.set(closedTarget);\n      return undefined;\n    }\n\n    const controls = animate(translateMotionValue, target, springTransition);\n\n    // Keep snap geometry correct after viewport or custom-size changes.\n    const observer =\n      typeof ResizeObserver === \"undefined\"\n        ? null\n        : new ResizeObserver(() => {\n            const size = measureContentSize(\n              contentRef,\n              axis,\n              fallbackContentSize,\n            );\n            if (size === contentSizeValue.get()) return;\n            controls.stop();\n            contentSizeValue.set(size);\n            translateMotionValue.set(\n              (open ? 1 - resolvedSnapPoint : 1) * size * closingSign,\n            );\n          });\n    if (contentRef.current) observer?.observe(contentRef.current);\n\n    return () => {\n      controls.stop();\n      observer?.disconnect();\n    };\n  }, [\n    open,\n    resolvedSnapPoint,\n    axis,\n    closingSign,\n    fallbackContentSize,\n    translateMotionValue,\n    springTransition,\n    contentRef,\n    contentSizeValue,\n  ]);\n\n  useEffect(() => {\n    const controls = animate(\n      scaleMotionValue,\n      receded ? recedeScale : 1,\n      springTransition,\n    );\n\n    const offsetControls = animate(\n      recedeOffset,\n      receded ? -40 * closingSign : 0,\n      springTransition,\n    );\n    return () => {\n      controls.stop();\n      offsetControls.stop();\n    };\n  }, [\n    receded,\n    recedeScale,\n    scaleMotionValue,\n    springTransition,\n    recedeOffset,\n    closingSign,\n  ]);\n\n  function commitSnapPoint(nextSnapPoint: number) {\n    if (!isControlled) {\n      setUncontrolledSnapPoint(nextSnapPoint);\n    }\n\n    onActiveSnapPointChange?.(nextSnapPoint);\n\n    if (nextSnapPoint === resolvedSnapPoint) {\n      const contentSize = measureContentSize(\n        contentRef,\n        axis,\n        fallbackContentSize,\n      );\n      const target = (1 - nextSnapPoint) * contentSize * closingSign;\n\n      animate(translateMotionValue, target, springTransition);\n    }\n  }\n\n  function handleDragEnd(_event: PointerEvent, info: PanInfo) {\n    const rawVelocity = axis === \"x\" ? info.velocity.x : info.velocity.y;\n    const signedVelocity = rawVelocity * closingSign;\n    const contentSize = measureContentSize(contentRef, axis);\n\n    if (contentSize <= 0) {\n      return;\n    }\n\n    if (\n      shouldDismissDrawerFromVelocity({\n        velocity: signedVelocity,\n        velocityThreshold,\n      })\n    ) {\n      onOpenChange(false);\n      return;\n    }\n\n    const currentTranslate = translateMotionValue.get();\n    const draggedOpenFraction = clampSnapPointFraction(\n      1 - (currentTranslate * closingSign) / contentSize,\n    );\n    const targetStop = resolveDrawerDragReleaseStop({\n      closeThreshold,\n      draggedOpenFraction,\n      startOpenFraction: startSnapPointRef.current,\n      stops,\n    });\n\n    if (targetStop <= 0) {\n      onOpenChange(false);\n      return;\n    }\n\n    commitSnapPoint(targetStop);\n  }\n\n  function getMotionProps(dragHandleOnly: boolean): DrawerDragMotionProps {\n    const openSide: \"top\" | \"left\" = axis === \"x\" ? \"left\" : \"top\";\n    const closeSide: \"bottom\" | \"right\" = axis === \"x\" ? \"right\" : \"bottom\";\n    const constraintSide = closingSign > 0 ? openSide : closeSide;\n\n    return {\n      drag: open ? axis : false,\n      dragConstraints: { [constraintSide]: 0 },\n      dragControls,\n      dragElastic: DRAWER_DRAG_ELASTIC,\n      dragListener: !dragHandleOnly,\n      dragMomentum: false,\n      onDragEnd: handleDragEnd,\n      style: {\n        ...(axis === \"x\"\n          ? { scale: scaleMotionValue, x: translateMotionValue }\n          : { scale: scaleMotionValue, y: translateMotionValue }),\n        translate: recedeTranslate,\n        ...(hasSnapPoints ? { \"--drawer-visible-size\": visibleSize } : {}),\n      },\n    } as unknown as DrawerDragMotionProps;\n  }\n\n  return {\n    activeSnapPoint: resolvedSnapPoint,\n    dragControls,\n    getMotionProps,\n    hasSnapPoints,\n    overlayOpacity,\n  };\n}\n\nconst drawerHandleBaseClasses =\n  \"relative mx-auto flex h-8 w-full shrink-0 touch-none cursor-grab items-center justify-center data-[direction=left]:absolute data-[direction=left]:inset-y-0 data-[direction=left]:right-0 data-[direction=left]:z-10 data-[direction=left]:mx-0 data-[direction=left]:my-auto data-[direction=left]:h-full data-[direction=left]:w-8 data-[direction=right]:absolute data-[direction=right]:inset-y-0 data-[direction=right]:left-0 data-[direction=right]:z-10 data-[direction=right]:mx-0 data-[direction=right]:my-auto data-[direction=right]:h-full data-[direction=right]:w-8 active:cursor-grabbing after:block after:h-1.5 after:w-12 after:rounded-full after:bg-muted-foreground/40 after:shadow-sm after:transition-[width,height,background-color,opacity] hover:after:bg-muted-foreground/55 active:after:w-14 data-[direction=left]:after:h-12 data-[direction=left]:after:w-1.5 data-[direction=left]:active:after:h-14 data-[direction=left]:active:after:w-1.5 data-[direction=right]:after:h-12 data-[direction=right]:after:w-1.5 data-[direction=right]:active:after:h-14 data-[direction=right]:active:after:w-1.5 motion-reduce:after:transition-none\";\n\nexport function drawerHandleClassNames({\n  className,\n}: { className?: string } = {}) {\n  return drawerHandleBaseClasses + (className ? ` ${className}` : \"\");\n}\n\nexport interface DrawerMotionHandleProps {\n  \"aria-label\"?: string;\n  className?: string;\n  direction: DrawerDirection;\n  dragControls: DragControls;\n}\n\nexport const DrawerMotionHandle = forwardRef<\n  HTMLDivElement,\n  DrawerMotionHandleProps\n>(({ className, direction, dragControls, ...props }, ref) => (\n  <motion.div\n    {...props}\n    ref={ref}\n    aria-hidden=\"true\"\n    data-direction={direction}\n    data-slot=\"drawer-handle\"\n    className={drawerHandleClassNames({ className })}\n    onPointerDown={(event) => {\n      dragControls.start(event);\n    }}\n  />\n));\n\nDrawerMotionHandle.displayName = \"DrawerMotionHandle\";\n\nexport const DRAWER_DEFAULT_EDGE_SWIPE_HIT_REGION_SIZE = 24;\n\nconst drawerEdgeSwipeZoneSideClasses: Record<DrawerDirection, string> = {\n  bottom: \"inset-x-0 bottom-0\",\n  left: \"inset-y-0 left-0\",\n  right: \"inset-y-0 right-0\",\n  top: \"inset-x-0 top-0\",\n};\n\nconst drawerEdgeSwipeZoneBaseClasses = \"fixed z-40 touch-none\";\n\nexport interface DrawerEdgeSwipeZoneProps {\n  direction: DrawerDirection;\n  hitRegionSize: number;\n  onOpenChange: (open: boolean) => void;\n  velocityThreshold: number;\n}\n\n/**\n * An always-mounted, invisible strip pinned to the drawer's own `direction`\n * edge. Only rendered while the drawer is closed (see drawer.tsx), it opens\n * the drawer once a pan gesture crosses `hitRegionSize` of travel toward the\n * interior, or a fast flick crosses `velocityThreshold` — the same\n * distance-or-velocity shape as the drag-to-dismiss decision in\n * `useDrawerDrag`, applied in the opposite (opening) direction. It never\n * uses `drag` (which would visually move the zone); `onPanEnd` only\n * recognizes the gesture and reports offset/velocity.\n */\nexport function DrawerEdgeSwipeZone({\n  direction,\n  hitRegionSize,\n  onOpenChange,\n  velocityThreshold,\n}: DrawerEdgeSwipeZoneProps) {\n  const axis = drawerDragAxis(direction);\n  // Dragging opens the drawer from the edge opposite its closing direction.\n  const openingSign = -drawerDragClosingSign(direction) as 1 | -1;\n\n  function handlePanEnd(_event: PointerEvent, info: PanInfo) {\n    const rawOffset = axis === \"x\" ? info.offset.x : info.offset.y;\n    const rawVelocity = axis === \"x\" ? info.velocity.x : info.velocity.y;\n    const signedOffset = rawOffset * openingSign;\n    const signedVelocity = rawVelocity * openingSign;\n\n    if (signedOffset >= hitRegionSize || signedVelocity >= velocityThreshold) {\n      onOpenChange(true);\n    }\n  }\n\n  return (\n    <motion.div\n      aria-hidden=\"true\"\n      className={cn(\n        drawerEdgeSwipeZoneBaseClasses,\n        drawerEdgeSwipeZoneSideClasses[direction],\n      )}\n      data-direction={direction}\n      data-slot=\"drawer-edge-swipe-zone\"\n      onPanEnd={handlePanEnd}\n      style={\n        axis === \"x\" ? { width: hitRegionSize } : { height: hitRegionSize }\n      }\n    />\n  );\n}\n"
    },
    {
      "path": "packages/components/src/components/drawer/drawer-background-scale.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/drawer-background-scale.ts",
      "content": "import { useEffect, useRef } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Consumers mark their app-root wrapper with this attribute, parallel to\n * vaul's `data-vaul-drawer-wrapper` convention. Drawer never renders or\n * owns this element itself (it usually sits far above Drawer in the tree,\n * as a sibling of the provider-aware portal container), so state is\n * communicated by toggling a second attribute on whatever element carries\n * this marker rather than through React context.\n */\nexport const DRAWER_BACKGROUND_WRAPPER_ATTRIBUTE =\n  \"data-drawer-background-wrapper\";\nexport const DRAWER_BACKGROUND_SCALE_ATTRIBUTE = \"data-drawer-background-scale\";\n\ntype DrawerBackgroundScaleValue = \"scaled\" | \"dimmed\";\n\n/**\n * Two independent counters (not one shared set) so a reduced-motion drawer\n * closing never clears the \"scaled\" state contributed by another,\n * motion-enabled drawer that is still open, and vice versa.\n */\nconst scaledDrawers = new Set<symbol>();\nconst dimmedDrawers = new Set<symbol>();\n\nfunction resolveBackgroundWrapper(): HTMLElement | null {\n  if (typeof document === \"undefined\") {\n    return null;\n  }\n\n  return document.querySelector<HTMLElement>(\n    `[${DRAWER_BACKGROUND_WRAPPER_ATTRIBUTE}]`,\n  );\n}\n\nfunction syncBackgroundWrapperAttribute() {\n  const wrapper = resolveBackgroundWrapper();\n\n  if (!wrapper) {\n    return;\n  }\n\n  const nextValue: DrawerBackgroundScaleValue | undefined =\n    scaledDrawers.size > 0\n      ? \"scaled\"\n      : dimmedDrawers.size > 0\n        ? \"dimmed\"\n        : undefined;\n\n  if (nextValue) {\n    wrapper.setAttribute(DRAWER_BACKGROUND_SCALE_ATTRIBUTE, nextValue);\n  } else {\n    wrapper.removeAttribute(DRAWER_BACKGROUND_SCALE_ATTRIBUTE);\n  }\n}\n\nexport interface UseDrawerBackgroundScaleOptions {\n  /** `backgroundScale && modal && open`, pre-combined by the caller. */\n  active: boolean;\n  reducedMotion: boolean;\n}\n\n/**\n * Registers this Drawer instance's contribution to the page-level\n * background-scale state while `active`, and clears it on change/unmount.\n * Reduced motion still dims the wrapper — it only drops the scale\n * transform — so it registers into a separate counter rather than skipping\n * registration entirely.\n */\nexport function useDrawerBackgroundScale({\n  active,\n  reducedMotion,\n}: UseDrawerBackgroundScaleOptions): void {\n  const idRef = useRef<symbol | null>(null);\n\n  if (idRef.current === null) {\n    idRef.current = Symbol(\"drawer-background-scale\");\n  }\n\n  useEffect(() => {\n    if (!active) {\n      return undefined;\n    }\n\n    const id = idRef.current!;\n    const set = reducedMotion ? dimmedDrawers : scaledDrawers;\n\n    set.add(id);\n    syncBackgroundWrapperAttribute();\n\n    return () => {\n      set.delete(id);\n      syncBackgroundWrapperAttribute();\n    };\n  }, [active, reducedMotion]);\n}\n\nconst drawerBackgroundWrapperBaseClasses =\n  \"origin-top transition-[transform,filter] duration-200 ease-out motion-reduce:transition-[filter]\";\n\nconst drawerBackgroundWrapperScaledClasses =\n  \"data-[drawer-background-scale=scaled]:overflow-hidden data-[drawer-background-scale=scaled]:rounded-[var(--dt-radius-lg)] data-[drawer-background-scale=scaled]:scale-[0.95] data-[drawer-background-scale=scaled]:brightness-90\";\n\nconst drawerBackgroundWrapperDimmedClasses =\n  \"data-[drawer-background-scale=dimmed]:brightness-90\";\n\nexport function drawerBackgroundWrapperClassNames({\n  className,\n}: { className?: string } = {}) {\n  return cn(\n    drawerBackgroundWrapperBaseClasses,\n    drawerBackgroundWrapperScaledClasses,\n    drawerBackgroundWrapperDimmedClasses,\n    className,\n  );\n}\n"
    },
    {
      "path": "packages/components/src/components/drawer/drawer-snap.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/drawer-snap.ts",
      "content": "import type { DrawerDirection } from \"./drawer-types\";\n\nexport type DrawerSnapPoint = number;\n\nexport const DRAWER_DEFAULT_CLOSE_THRESHOLD = 0.25;\nexport const DRAWER_DEFAULT_VELOCITY_THRESHOLD = 500;\n\nexport function clampSnapPointFraction(value: number): number {\n  if (!Number.isFinite(value)) {\n    return 0;\n  }\n\n  return Math.min(1, Math.max(0, value));\n}\n\nexport function drawerDragAxis(direction: DrawerDirection): \"x\" | \"y\" {\n  return direction === \"left\" || direction === \"right\" ? \"x\" : \"y\";\n}\n\nexport function drawerDragClosingSign(direction: DrawerDirection): 1 | -1 {\n  return direction === \"bottom\" || direction === \"right\" ? 1 : -1;\n}\n\n/**\n * Stops are fractions of the drawer's open size, always including `0`\n * (closed). Snap points above `0` are deduped, clamped, and sorted; the\n * largest configured value is the maximum reachable open position, not an\n * implicit full-size stop — a drawer configured with `snapPoints={[0.3,\n * 0.6]}` cannot rest fully open by dragging past `0.6`.\n */\nexport function resolveDrawerSnapStops(\n  snapPoints: DrawerSnapPoint[] | undefined,\n): number[] {\n  const source = snapPoints && snapPoints.length > 0 ? snapPoints : [1];\n  const openStops = source\n    .map(clampSnapPointFraction)\n    .filter((value) => value > 0);\n  const unique = Array.from(new Set([0, ...openStops]));\n\n  return unique.sort((a, b) => a - b);\n}\n\nexport function findNearestDrawerSnapStop(\n  value: number,\n  stops: number[],\n): number {\n  let nearest = stops[0] ?? 1;\n  let smallestDiff = Number.POSITIVE_INFINITY;\n\n  for (const stop of stops) {\n    const diff = Math.abs(stop - value);\n\n    if (diff < smallestDiff) {\n      smallestDiff = diff;\n      nearest = stop;\n    }\n  }\n\n  return nearest;\n}\n\nexport function resolveDrawerActiveSnapStop({\n  activeSnapPoint,\n  defaultSnapPoint,\n  stops,\n}: {\n  activeSnapPoint?: number;\n  defaultSnapPoint?: number;\n  stops: number[];\n}): number {\n  const openStops = stops.filter((stop) => stop > 0);\n  const fallback = openStops[openStops.length - 1] ?? 1;\n  const candidate = activeSnapPoint ?? defaultSnapPoint ?? fallback;\n\n  return findNearestDrawerSnapStop(\n    clampSnapPointFraction(candidate),\n    openStops.length > 0 ? openStops : [fallback],\n  );\n}\n\nexport function shouldDismissDrawerFromVelocity({\n  velocity,\n  velocityThreshold,\n}: {\n  velocity: number;\n  velocityThreshold: number;\n}): boolean {\n  return velocity >= velocityThreshold;\n}\n\n/**\n * Resolves which stop a drag release should settle at, given the fraction\n * dragged relative to the stop active when the drag began. Distance is\n * measured from the starting stop (not the nearest stop overall), so\n * `closeThreshold` behaves as \"how far past your current rest position\n * before the next stop commits\" rather than pure nearest-neighbor snapping.\n * A returned stop of `0` means the drawer should close.\n */\nexport function resolveDrawerDragReleaseStop({\n  closeThreshold,\n  draggedOpenFraction,\n  startOpenFraction,\n  stops,\n}: {\n  closeThreshold: number;\n  draggedOpenFraction: number;\n  startOpenFraction: number;\n  stops: number[];\n}): number {\n  const sortedStops = [...stops].sort((a, b) => a - b);\n  const clampedDragged = clampSnapPointFraction(draggedOpenFraction);\n\n  if (clampedDragged === startOpenFraction) {\n    return startOpenFraction;\n  }\n\n  const distance = Math.abs(clampedDragged - startOpenFraction);\n\n  if (distance < closeThreshold) {\n    return startOpenFraction;\n  }\n\n  const startIndex = sortedStops.indexOf(startOpenFraction);\n  const movingToward = clampedDragged < startOpenFraction ? -1 : 1;\n  const nextIndex = startIndex + movingToward;\n\n  if (startIndex === -1) {\n    return findNearestDrawerSnapStop(clampedDragged, sortedStops);\n  }\n\n  if (nextIndex < 0) {\n    return sortedStops[0] ?? 0;\n  }\n\n  if (nextIndex >= sortedStops.length) {\n    return sortedStops[sortedStops.length - 1] ?? startOpenFraction;\n  }\n\n  return sortedStops[nextIndex]!;\n}\n"
    },
    {
      "path": "packages/components/src/components/drawer/drawer-types.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/drawer-types.ts",
      "content": "export type DrawerDirection = \"top\" | \"bottom\" | \"left\" | \"right\";\nexport type DrawerMotionPreset = \"none\" | \"subtle\" | \"standard\" | \"expressive\";\n"
    },
    {
      "path": "packages/components/src/components/drawer/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/drawer/index.ts",
      "content": "export {\n  Drawer,\n  DrawerClose,\n  DrawerContent,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHandle,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerTrigger,\n  drawerClassNames,\n  drawerCloseButtonClassNames,\n  drawerContentClassNames,\n  drawerDescriptionClassNames,\n  drawerFooterClassNames,\n  drawerHeaderClassNames,\n  drawerOverlayClassNames,\n  drawerTitleClassNames,\n  drawerTriggerClassNames,\n  type DrawerCloseProps,\n  type DrawerCloseRenderProps,\n  type DrawerContentProps,\n  type DrawerDescriptionProps,\n  type DrawerDimension,\n  type DrawerDirection,\n  type DrawerFooterProps,\n  type DrawerHandleProps,\n  type DrawerHeaderProps,\n  type DrawerProps,\n  type DrawerScrollBehavior,\n  type DrawerSize,\n  type DrawerSnapPoint,\n  type DrawerTitleProps,\n  type DrawerTriggerProps,\n} from \"./drawer\";\nexport {\n  DRAWER_BACKGROUND_SCALE_ATTRIBUTE,\n  DRAWER_BACKGROUND_WRAPPER_ATTRIBUTE,\n  drawerBackgroundWrapperClassNames,\n} from \"./drawer-background-scale\";\nexport { DRAWER_DEFAULT_EDGE_SWIPE_HIT_REGION_SIZE } from \"./drawer-motion\";\n"
    },
    {
      "path": "packages/components/src/utils/provider-portal.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/utils/provider-portal.tsx",
      "content": "import {\n  createContext,\n  type ForwardedRef,\n  type ReactNode,\n  type RefCallback,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria/PortalProvider\";\nimport { setDethinkPortalOwner } from \"./portal-owner\";\n\nconst providerPortalDefaultClasses = \"bg-background font-sans text-foreground\";\nconst providerPortalMirroredAttributes = [\n  \"data-theme\",\n  \"data-density\",\n  \"dir\",\n] as const;\n\nexport type ProviderPortalMirroredAttribute =\n  (typeof providerPortalMirroredAttributes)[number];\n\nexport interface ProviderPortalRootOptions<T extends HTMLElement> {\n  forwardedRef?: ForwardedRef<T>;\n  portalSlot: string;\n}\n\nexport interface ProviderPortalRootState<T extends HTMLElement> {\n  portalContainer: HTMLElement | null;\n  rootRef: RefCallback<T>;\n}\n\nexport interface DethinkPortalProviderProps {\n  children?: ReactNode;\n  container: HTMLElement | null;\n}\n\nconst DethinkPortalContext = createContext(false);\nconst OverlayVisibilityContext = createContext(true);\n\n/** Persistent surfaces can dismiss overlays without unmounting their content. */\nexport function DethinkOverlayScope({\n  visible,\n  children,\n}: {\n  visible: boolean;\n  children?: ReactNode;\n}) {\n  const parentVisible = useContext(OverlayVisibilityContext);\n  return (\n    <OverlayVisibilityContext.Provider value={visible && parentVisible}>\n      {children}\n    </OverlayVisibilityContext.Provider>\n  );\n}\n\nexport function useScopedOverlayState({\n  open,\n  defaultOpen = false,\n  onOpenChange,\n}: {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}): readonly [boolean, (open: boolean) => void] {\n  const visible = useContext(OverlayVisibilityContext);\n  const [localOpen, setLocalOpen] = useState(defaultOpen);\n  const requestedOpen = open ?? localOpen;\n  const closeRequested = useRef(false);\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (next && !visible) return;\n      if (open === undefined) setLocalOpen(next);\n      onOpenChange?.(next);\n    },\n    [open, onOpenChange, visible],\n  );\n  useEffect(() => {\n    if (visible) closeRequested.current = false;\n    else if (requestedOpen && !closeRequested.current) {\n      closeRequested.current = true;\n      // Synchronize overlay state with the containing surface's lifecycle.\n      setOpen(false);\n    }\n  }, [visible, requestedOpen, setOpen]);\n  return [visible && requestedOpen, setOpen];\n}\n\nfunction assignForwardedRef<T>(\n  ref: ForwardedRef<T> | undefined,\n  value: T | null,\n) {\n  if (typeof ref === \"function\") {\n    ref(value);\n  } else if (ref) {\n    ref.current = value;\n  }\n}\n\nfunction fallbackProviderAttribute(name: ProviderPortalMirroredAttribute) {\n  if (typeof document === \"undefined\") {\n    return null;\n  }\n\n  if (name === \"data-density\") {\n    return \"default\";\n  }\n\n  if (name === \"data-theme\") {\n    return document.documentElement.getAttribute(name) ?? \"system\";\n  }\n\n  return document.documentElement.getAttribute(\"dir\") ?? document.dir ?? \"ltr\";\n}\n\nexport function syncDethinkPortalContainer({\n  container,\n  portalSlot,\n  provider,\n}: {\n  container: HTMLElement;\n  portalSlot: string;\n  provider: HTMLElement | null;\n}) {\n  const source =\n    provider ??\n    (typeof document === \"undefined\" ? null : document.documentElement);\n\n  container.setAttribute(\"data-slot\", portalSlot);\n  container.setAttribute(\"data-dethink-provider\", \"\");\n  container.className = provider?.className || providerPortalDefaultClasses;\n  container.style.cssText = provider?.getAttribute(\"style\") ?? \"\";\n  container.style.display = \"contents\";\n\n  for (const attribute of providerPortalMirroredAttributes) {\n    const value =\n      source?.getAttribute(attribute) ?? fallbackProviderAttribute(attribute);\n\n    if (value) {\n      container.setAttribute(attribute, value);\n    } else {\n      container.removeAttribute(attribute);\n    }\n  }\n}\n\nexport function useProviderPortalRoot<T extends HTMLElement>({\n  forwardedRef,\n  portalSlot,\n}: ProviderPortalRootOptions<T>): ProviderPortalRootState<T> {\n  const [portalContainer] = useState<HTMLElement | null>(() => {\n    if (typeof document === \"undefined\") {\n      return null;\n    }\n\n    return document.createElement(\"div\");\n  });\n  const [rootElement, setRootElement] = useState<T | null>(null);\n\n  const rootRef = useCallback(\n    (node: T | null) => {\n      setRootElement(node);\n      assignForwardedRef(forwardedRef, node);\n\n      if (node && portalContainer && typeof document !== \"undefined\") {\n        setDethinkPortalOwner(portalContainer, node);\n        const provider =\n          node.closest<HTMLElement>(\"[data-dethink-provider]\") ?? null;\n\n        syncDethinkPortalContainer({\n          container: portalContainer,\n          portalSlot,\n          provider,\n        });\n\n        if (!portalContainer.isConnected) {\n          document.body.appendChild(portalContainer);\n        }\n      }\n    },\n    [forwardedRef, portalContainer, portalSlot],\n  );\n\n  useEffect(() => {\n    if (!portalContainer || typeof document === \"undefined\") {\n      return undefined;\n    }\n\n    const provider =\n      rootElement?.closest<HTMLElement>(\"[data-dethink-provider]\") ?? null;\n    const syncContainer = () => {\n      syncDethinkPortalContainer({\n        container: portalContainer,\n        portalSlot,\n        provider,\n      });\n    };\n\n    syncContainer();\n    document.body.appendChild(portalContainer);\n\n    const observerTarget = provider ?? document.documentElement;\n    const observer = new MutationObserver(syncContainer);\n    observer.observe(observerTarget, {\n      attributeFilter: [\"class\", \"data-density\", \"data-theme\", \"dir\", \"style\"],\n      attributes: true,\n    });\n\n    return () => {\n      observer.disconnect();\n      portalContainer.remove();\n    };\n  }, [portalContainer, portalSlot, rootElement]);\n\n  return {\n    portalContainer,\n    rootRef,\n  };\n}\n\nexport function DethinkPortalProvider({\n  children,\n  container,\n}: DethinkPortalProviderProps) {\n  const hasDethinkPortalParent = useContext(DethinkPortalContext);\n  const content = hasDethinkPortalParent ? (\n    children\n  ) : (\n    <UNSAFE_PortalProvider getContainer={() => container}>\n      {children}\n    </UNSAFE_PortalProvider>\n  );\n\n  return (\n    <DethinkPortalContext.Provider value>\n      {content}\n    </DethinkPortalContext.Provider>\n  );\n}\n"
    },
    {
      "path": "packages/components/src/utils/portal-owner.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/portal-owner.ts",
      "content": "/**\n * Links a Dethink portal container on <body> to the component root that owns\n * it, so a modal surface (the mobile sidebar) can keep overlays opened from\n * inside it usable while it makes the rest of the page inert. A global symbol\n * key, so components copied from the registry agree without sharing imports.\n */\nconst PORTAL_OWNER: unique symbol = Symbol.for(\"dethink.portal-owner\");\n\ntype OwnedElement = Element & { [PORTAL_OWNER]?: Element };\n\nexport function setDethinkPortalOwner(container: Element, owner: Element) {\n  (container as OwnedElement)[PORTAL_OWNER] = owner;\n}\n\n/** The element owning a Dethink portal container, or null for any other element. */\nexport function getDethinkPortalOwner(element: Element): Element | null {\n  return (element as OwnedElement)[PORTAL_OWNER] ?? null;\n}\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
