{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"drawer","title":"Drawer","description":"Swipe-aware modal panel anchored to a viewport edge.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/checkbox","@siteplane/menu","@siteplane/radio-group","@siteplane/scroll-area","@siteplane/switch"],"files":[{"path":"packages/ui/src/components/drawer/drawer.tsx","content":"\"use client\";\n\nimport { Drawer as DrawerPrimitive } from \"@base-ui/react/drawer\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { ChevronRightIcon, XIcon } from \"lucide-react\";\nimport type React from \"react\";\nimport { createContext, useContext } from \"react\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport { surfaceClassNames } from \"../../system/styles/surface\";\nimport { KitButton } from \"../button/button-internal\";\nimport {\n  menuGroupLabelClassName,\n  menuSeparatorClassName,\n} from \"./drawer-menu-shared\";\nimport { type CheckboxProps } from \"../checkbox\";\nimport { KitCheckbox } from \"../checkbox/checkbox-internal\";\nimport { type RadioGroupProps, type RadioProps } from \"../radio-group\";\nimport { KitRadioGroup, KitRadio } from \"../radio-group/radio-group-internal\";\nimport { ScrollArea } from \"../scroll-area\";\nimport { type SwitchProps } from \"../switch\";\nimport { KitSwitch } from \"../switch/switch-internal\";\nimport {\n  type SiteplaneControlSize,\n  SiteplaneUIPortalScope,\n  useResolvedControlSize,\n} from \"../../system/provider/siteplane-ui-provider\";\n\nimport \"./drawer.css\";\n\ntype DrawerPosition = \"right\" | \"left\" | \"top\" | \"bottom\";\n\nconst DrawerContext: React.Context<{ position: DrawerPosition }> =\n  createContext<{ position: DrawerPosition }>({\n    position: \"bottom\",\n  });\n\nconst directionMap: Record<\n  DrawerPosition,\n  DrawerPrimitive.Root.Props[\"swipeDirection\"]\n> = {\n  bottom: \"down\",\n  left: \"left\",\n  right: \"right\",\n  top: \"up\",\n};\n\nexport const DrawerCreateHandle: typeof DrawerPrimitive.createHandle =\n  DrawerPrimitive.createHandle;\n\nexport function Drawer({\n  swipeDirection,\n  position = \"bottom\",\n  ...props\n}: DrawerPrimitive.Root.Props & {\n  position?: DrawerPosition;\n}): React.ReactElement {\n  return (\n    <DrawerContext.Provider value={{ position }}>\n      <DrawerPrimitive.Root\n        swipeDirection={swipeDirection ?? directionMap[position]}\n        {...props}\n      />\n    </DrawerContext.Provider>\n  );\n}\n\n// Base UI's own Portal type, with className narrowed to a string.\nexport const DrawerPortal: (\n  props: WithStringClassName<Omit<DrawerPrimitive.Portal.Props, \"ref\">> &\n    React.RefAttributes<HTMLDivElement>,\n) => React.JSX.Element | null = DrawerPrimitive.Portal;\n\nexport function DrawerTrigger(\n  props: WithStringClassName<DrawerPrimitive.Trigger.Props>,\n): React.ReactElement {\n  return (\n    <DrawerPrimitive.Trigger\n      data-slot=\"drawer-trigger\"\n      {...props}\n      className={cn(slotClass(\"drawer-trigger\", props), props.className)}\n    />\n  );\n}\n\nexport function DrawerClose(\n  props: WithStringClassName<DrawerPrimitive.Close.Props>,\n): React.ReactElement {\n  return (\n    <DrawerPrimitive.Close\n      data-slot=\"drawer-close\"\n      {...props}\n      className={cn(slotClass(\"drawer-close\", props), props.className)}\n    />\n  );\n}\n\nexport function DrawerSwipeArea({\n  className,\n  position: positionProp,\n  ...props\n}: WithStringClassName<DrawerPrimitive.SwipeArea.Props> & {\n  position?: DrawerPosition;\n}): React.ReactElement {\n  const { position: contextPosition } = useContext(DrawerContext);\n  const position = positionProp ?? contextPosition;\n\n  return (\n    <DrawerPrimitive.SwipeArea\n      className={cn(\n        slotClass(\"drawer-swipe-area\", props),\n        \"fixed z-50 touch-none\",\n        position === \"bottom\" && \"inset-x-0 bottom-0 h-8\",\n        position === \"top\" && \"inset-x-0 top-0 h-8\",\n        position === \"left\" && \"inset-y-0 left-0 w-8\",\n        position === \"right\" && \"inset-y-0 right-0 w-8\",\n        className,\n      )}\n      data-slot=\"drawer-swipe-area\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerBackdrop({\n  className,\n  ...props\n}: WithStringClassName<DrawerPrimitive.Backdrop.Props>): React.ReactElement {\n  return (\n    <DrawerPrimitive.Backdrop\n      className={cn(\n        // The fade is drawer.css's, so reduced motion can make it instant.\n        slotClass(\"drawer-backdrop\", props),\n        \"fixed inset-0 z-50 opacity-[calc(1-var(--drawer-swipe-progress))] data-ending-style:opacity-0 data-starting-style:opacity-0 supports-[-webkit-touch-callout:none]:absolute\",\n        className,\n      )}\n      data-slot=\"drawer-backdrop\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerViewport({\n  className,\n  position,\n  variant = \"default\",\n  ...props\n}: WithStringClassName<DrawerPrimitive.Viewport.Props> & {\n  position?: DrawerPosition;\n  variant?: \"default\" | \"straight\" | \"inset\";\n}): React.ReactElement {\n  return (\n    <DrawerPrimitive.Viewport\n      className={cn(\n        slotClass(\"drawer-viewport\", props),\n        \"fixed inset-0 z-50 [--bleed:--spacing(12)] [--inset:0px]\",\n        \"touch-none\",\n        position === \"bottom\" && \"grid grid-rows-[1fr_auto] pt-12\",\n        position === \"top\" && \"grid grid-rows-[auto_1fr] pb-12\",\n        position === \"left\" && \"flex justify-start\",\n        position === \"right\" && \"flex justify-end\",\n        variant === \"inset\" && \"px-(--inset) sm:[--inset:--spacing(4)]\",\n        variant === \"inset\" && position !== \"bottom\" && \"pt-(--inset)\",\n        variant === \"inset\" && position !== \"top\" && \"pb-(--inset)\",\n        className,\n      )}\n      data-slot=\"drawer-viewport\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerPopup({\n  className,\n  children,\n  elevation = \"default\",\n  showCloseButton = false,\n  position: positionProp,\n  variant = \"default\",\n  width,\n  showBar = false,\n  closeProps,\n  portalProps,\n  ...props\n}: WithStringClassName<DrawerPrimitive.Popup.Props> & {\n  elevation?: \"default\" | \"none\";\n  showCloseButton?: boolean;\n  position?: DrawerPosition;\n  variant?: \"default\" | \"straight\" | \"inset\";\n  /**\n   * Width of a bottom or top panel on a wide screen. `readable` caps it at\n   * `max-w-lg` (512 px) and centres it; `full` spans the whole edge. A `straight` panel stays attached along the whole edge, so it is\n   * `full` unless you ask for `readable`. Left and right panels keep their own\n   * width.\n   */\n  width?: \"readable\" | \"full\";\n  showBar?: boolean;\n  closeProps?: WithStringClassName<DrawerPrimitive.Close.Props>;\n  portalProps?: WithStringClassName<DrawerPrimitive.Portal.Props>;\n}): React.ReactElement {\n  const { position: contextPosition } = useContext(DrawerContext);\n  const position = positionProp ?? contextPosition;\n  const resolvedElevation = variant === \"straight\" ? \"none\" : elevation;\n  const resolvedWidth = width ?? (variant === \"straight\" ? \"full\" : \"readable\");\n\n  return (\n    <DrawerPortal {...portalProps}>\n      <SiteplaneUIPortalScope>\n        <DrawerBackdrop />\n        <DrawerViewport position={position} variant={variant}>\n          <DrawerPrimitive.Popup\n            className={cn(\n              slotClass(\"drawer-popup\", props),\n              surfaceClassNames.edgeOverlay,\n              \"flex max-h-full min-h-0 w-full min-w-0 flex-col outline-none will-change-transform [--peek:calc(--spacing(6)-1px)] [--scale-base:calc(max(0,1-(var(--nested-drawers)*var(--stack-step))))] [--scale:clamp(0,calc(var(--scale-base)+(var(--stack-step)*var(--stack-progress))),1)] [--shrink:calc(1-var(--scale))] [--stack-peek-offset:max(0px,calc((var(--nested-drawers)-var(--stack-progress))*var(--peek)))] [--stack-progress:clamp(0,var(--drawer-swipe-progress),1)] [--stack-step:0.05] after:pointer-events-none after:absolute data-swiping:select-none data-nested-drawer-open:overflow-hidden data-nested-drawer-open:bg-[color-mix(in_srgb,var(--sp-rw-surface-color),var(--color-black)_calc(2%*(var(--nested-drawers)-var(--stack-progress))))] data-ending-style:shadow-transparent data-starting-style:shadow-transparent dark:data-nested-drawer-open:bg-[color-mix(in_srgb,var(--sp-rw-surface-color),var(--color-black)_calc(6%*(var(--nested-drawers)-var(--stack-progress))))]\",\n              \"touch-none\",\n              position === \"bottom\" &&\n                \"transform-[translateY(calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y)))] data-ending-style:transform-[translateY(calc(100%+env(safe-area-inset-bottom,0px)+var(--inset)))] data-starting-style:transform-[translateY(calc(100%+env(safe-area-inset-bottom,0px)+var(--inset)))] row-start-2 -mb-[max(0px,calc(var(--drawer-snap-point-offset,0px)+clamp(0,1,var(--drawer-snap-point-offset,0px)/1px)*var(--drawer-swipe-movement-y,0px)))] pb-[max(0px,calc(env(safe-area-inset-bottom,0px)+var(--drawer-snap-point-offset,0px)+clamp(0,1,var(--drawer-snap-point-offset,0px)/1px)*var(--drawer-swipe-movement-y,0px)))] after:inset-x-0 after:top-full after:h-(--bleed) has-data-[slot=drawer-bar]:pt-2 data-ending-style:mb-0 data-starting-style:mb-0 data-ending-style:pb-0 data-starting-style:pb-0\",\n              position === \"top\" &&\n                \"data-starting-style:transform-[translateY(calc(-100%-var(--inset)))] data-ending-style:transform-[translateY(calc(-100%-var(--inset)))] transform-[translateY(var(--drawer-swipe-movement-y))] after:inset-x-0 after:bottom-full after:h-(--bleed) has-data-[slot=drawer-bar]:pb-2\",\n              position === \"left\" &&\n                \"data-starting-style:transform-[translateX(calc(-100%-var(--inset)))] data-ending-style:transform-[translateX(calc(-100%-var(--inset)))] transform-[translateX(var(--drawer-swipe-movement-x))] w-[calc(100%-(--spacing(12)))] max-w-md after:inset-y-0 after:end-full after:w-(--bleed) has-data-[slot=drawer-bar]:pe-2\",\n              position === \"right\" &&\n                \"transform-[translateX(var(--drawer-swipe-movement-x))] data-ending-style:transform-[translateX(calc(100%+var(--inset)))] data-starting-style:transform-[translateX(calc(100%+var(--inset)))] col-start-2 w-[calc(100%-(--spacing(12)))] max-w-md after:inset-y-0 after:start-full after:w-(--bleed) has-data-[slot=drawer-bar]:ps-2\",\n              variant === \"default\" &&\n                cn(\n                  position === \"bottom\" && \"before:rounded-t-[inherit]\",\n                  position === \"top\" && \"before:rounded-b-[inherit]\",\n                  position === \"left\" && \"before:rounded-e-[inherit]\",\n                  position === \"right\" && \"before:rounded-s-[inherit]\",\n                ),\n              variant === \"inset\" && \"sm:before:rounded-[inherit]\",\n              variant === \"straight\" && \"[--stack-step:0]\",\n              (position === \"bottom\" || position === \"top\") &&\n                \"h-(--drawer-height,auto) [--height:max(0px,calc(var(--drawer-frontmost-height,var(--drawer-height))))] data-nested-drawer-open:h-(--height)\",\n              // On a wide screen a bottom or top sheet keeps a readable width\n              // of 512 px and centres itself; `width=\"full\"` lets it span the\n              // whole edge, as a straight sheet does by default.\n              (position === \"bottom\" || position === \"top\") &&\n                resolvedWidth === \"readable\" &&\n                \"mx-auto max-w-lg\",\n              position === \"bottom\" &&\n                \"data-nested-drawer-open:transform-[translateY(calc(var(--drawer-swipe-movement-y)-var(--stack-peek-offset)-(var(--shrink)*var(--height))))_scale(var(--scale))] origin-[50%_calc(100%-var(--inset))]\",\n              position === \"top\" &&\n                \"data-nested-drawer-open:transform-[translateY(calc(var(--drawer-swipe-movement-y)+var(--stack-peek-offset)+(var(--shrink)*var(--height))))_scale(var(--scale))] origin-[50%_var(--inset)]\",\n              position === \"left\" &&\n                \"data-nested-drawer-open:transform-[translateX(calc(var(--drawer-swipe-movement-x)+var(--stack-peek-offset)))_scale(var(--scale))] origin-right\",\n              position === \"right\" &&\n                \"data-nested-drawer-open:transform-[translateX(calc(var(--drawer-swipe-movement-x)-var(--stack-peek-offset)))_scale(var(--scale))] origin-left\",\n              className,\n            )}\n            data-position={position}\n            data-elevation={resolvedElevation}\n            data-slot=\"drawer-popup\"\n            data-variant={variant}\n            {...props}\n          >\n            {children}\n            {/* The grab strip spans its whole edge; the close button comes\n                after it, so it paints and takes clicks above the strip. */}\n            {showBar && <DrawerBar />}\n            {showCloseButton && (\n              <DrawerPrimitive.Close\n                aria-label=\"Close\"\n                className=\"absolute end-2 top-2\"\n                render={\n                  <KitButton\n                    profile=\"overlay-dismiss\"\n                    shape=\"square\"\n                    variant=\"ghost\"\n                  />\n                }\n                {...closeProps}\n              >\n                <XIcon />\n              </DrawerPrimitive.Close>\n            )}\n          </DrawerPrimitive.Popup>\n        </DrawerViewport>\n      </SiteplaneUIPortalScope>\n    </DrawerPortal>\n  );\n}\n\n// Base UI's bare content part: in it a pointer selects text instead of\n// swiping the drawer. The header, footer and panel take it with\n// allowSelection.\nconst SelectableContent = DrawerPrimitive.Content;\n\nexport function DrawerHeader({\n  className,\n  allowSelection = false,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  allowSelection?: boolean;\n}): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-header\", props, render),\n      \"flex flex-col\",\n      !allowSelection && \"cursor-default\",\n      className,\n    ),\n    \"data-slot\": \"drawer-header\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render: allowSelection ? <SelectableContent render={render} /> : render,\n  });\n}\n\nexport function DrawerFooter({\n  className,\n  allowSelection = true,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  allowSelection?: boolean;\n}): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      // Actions that do not fit wrap onto another row with the same gap instead\n      // of overflowing a narrow panel.\n      slotClass(\"drawer-footer\", props, render),\n      \"flex flex-wrap\",\n      !allowSelection && \"cursor-default\",\n      className,\n    ),\n    \"data-slot\": \"drawer-footer\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render: allowSelection ? <SelectableContent render={render} /> : render,\n  });\n}\n\nexport function DrawerTitle({\n  className,\n  ...props\n}: WithStringClassName<DrawerPrimitive.Title.Props>): React.ReactElement {\n  return (\n    <DrawerPrimitive.Title\n      className={cn(\n        slotClass(\"drawer-title\", props),\n        \"font-heading wrap-anywhere\",\n        className,\n      )}\n      data-slot=\"drawer-title\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerDescription({\n  className,\n  ...props\n}: WithStringClassName<DrawerPrimitive.Description.Props>): React.ReactElement {\n  return (\n    <DrawerPrimitive.Description\n      className={cn(\n        slotClass(\"drawer-description\", props),\n        \"wrap-anywhere\",\n        className,\n      )}\n      data-slot=\"drawer-description\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerPanel({\n  className,\n  scrollFade = true,\n  scrollable = true,\n  allowSelection = true,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  scrollFade?: boolean;\n  scrollable?: boolean;\n  allowSelection?: boolean;\n}): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-panel\", props, render),\n      !allowSelection && \"cursor-default\",\n      className,\n    ),\n    \"data-slot\": \"drawer-panel\",\n  };\n\n  const content = useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render: allowSelection ? <SelectableContent render={render} /> : render,\n  });\n\n  if (scrollable) {\n    return (\n      <ScrollArea className=\"touch-auto\" scrollFade={scrollFade}>\n        {content}\n      </ScrollArea>\n    );\n  }\n\n  return content;\n}\n\nexport function DrawerBar({\n  className,\n  position: positionProp,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  position?: DrawerPosition;\n}): React.ReactElement {\n  const { position: contextPosition } = useContext(DrawerContext);\n  const position = positionProp ?? contextPosition;\n  const horizontal = position === \"left\" || position === \"right\";\n  const defaultProps = {\n    \"aria-hidden\": true as const,\n    className: cn(\n      slotClass(\"drawer-bar\", props, render),\n      \"absolute flex cursor-grab touch-none items-center justify-center p-3 active:cursor-grabbing before:rounded-full\",\n      horizontal\n        ? \"inset-y-0 before:h-12 before:w-1\"\n        : \"inset-x-0 before:h-1 before:w-12\",\n      position === \"top\" && \"bottom-0\",\n      position === \"bottom\" && \"top-0\",\n      position === \"left\" && \"right-0\",\n      position === \"right\" && \"left-0\",\n      className,\n    ),\n    \"data-slot\": \"drawer-bar\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function DrawerMenu({\n  className,\n  render,\n  size,\n  ...props\n}: useRender.ComponentProps<\"nav\"> & {\n  /**\n   * The row size; it follows the provider's control size. `xl` is the touch\n   * size for navigation and sheets that are tapped: 48px rows with 16px text\n   * and 20px icons. The other sizes keep the compact menu rows, which a touch\n   * screen turns into 48px rows as well.\n   */\n  size?: SiteplaneControlSize;\n}): React.ReactElement {\n  const resolvedSize = useResolvedControlSize(size);\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-menu\", props, render),\n      \"-m-2 flex flex-col\",\n      className,\n    ),\n    \"data-size\": resolvedSize,\n    \"data-slot\": \"drawer-menu\",\n  };\n\n  return useRender({\n    defaultTagName: \"nav\",\n    props: mergeProps<\"nav\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function DrawerMenuItem({\n  className,\n  variant = \"default\",\n  render,\n  disabled,\n  ...props\n}: useRender.ComponentProps<\"button\"> & {\n  variant?: \"default\" | \"destructive\";\n}): React.ReactElement {\n  return (\n    <KitButton\n      align=\"start\"\n      className={className}\n      disabled={disabled}\n      multiline\n      profile=\"menu-row\"\n      render={render}\n      variant={variant === \"destructive\" ? \"ghost-destructive\" : \"text\"}\n      width=\"full\"\n      {...props}\n      // Last, so a host that renders the row, such as DrawerClose, cannot swap\n      // the slot that the row recipe and the touch size are keyed on.\n      data-slot=\"drawer-menu-item\"\n    />\n  );\n}\n\nexport function DrawerMenuSeparator({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-menu-separator\", props, render),\n      menuSeparatorClassName,\n      className,\n    ),\n    \"data-slot\": \"drawer-menu-separator\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function DrawerMenuGroup({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-menu-group\", props, render),\n      \"flex flex-col\",\n      className,\n    ),\n    \"data-slot\": \"drawer-menu-group\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function DrawerMenuGroupLabel({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"drawer-menu-group-label\", props, render),\n      menuGroupLabelClassName,\n      className,\n    ),\n    \"data-slot\": \"drawer-menu-group-label\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function DrawerMenuTrigger({\n  className,\n  children,\n  ...props\n}: Omit<\n  WithStringClassName<DrawerPrimitive.Trigger.Props>,\n  \"render\"\n>): React.ReactElement {\n  return (\n    <DrawerTrigger\n      className={className}\n      data-slot=\"drawer-menu-trigger\"\n      render={\n        <KitButton\n          align=\"start\"\n          multiline\n          profile=\"submenu-trigger\"\n          variant=\"text\"\n          width=\"full\"\n        />\n      }\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon />\n    </DrawerTrigger>\n  );\n}\n\nexport function DrawerMenuCheckboxItem({\n  variant = \"default\",\n  ...props\n}: Omit<CheckboxProps, \"profile\"> & {\n  variant?: \"default\" | \"switch\";\n}): React.ReactElement {\n  if (variant === \"switch\") {\n    return (\n      <KitSwitch\n        {...(props as SwitchProps)}\n        data-slot=\"drawer-menu-checkbox-item\"\n        profile=\"menu-item\"\n      />\n    );\n  }\n\n  return (\n    <KitCheckbox\n      {...props}\n      data-slot=\"drawer-menu-checkbox-item\"\n      profile=\"menu-item\"\n    />\n  );\n}\n\nexport function DrawerMenuRadioGroup({\n  className,\n  ...props\n}: Omit<RadioGroupProps, \"profile\">): React.ReactElement {\n  return (\n    <KitRadioGroup\n      className={className}\n      data-slot=\"drawer-menu-radio-group\"\n      profile=\"menu-item\"\n      {...props}\n    />\n  );\n}\n\nexport function DrawerMenuRadioItem({\n  indicatorPosition = \"start\",\n  ...props\n}: Omit<RadioProps, \"profile\"> & {\n  value: string;\n  /**\n   * Where the row shows its check: in the leading slot (`start`), or at the\n   * end of the row (`end`) for identity rows that lead with a logo or an\n   * avatar, such as the workspaces of a switcher.\n   */\n  indicatorPosition?: \"start\" | \"end\" | undefined;\n}): React.ReactElement {\n  return (\n    <KitRadio\n      data-slot=\"drawer-menu-radio-item\"\n      indicatorPosition={indicatorPosition}\n      profile=\"menu-item\"\n      {...props}\n    />\n  );\n}\n\n// DrawerContent is the shadcn name of DrawerPopup, as DialogContent is of\n// DialogPopup. Base UI's bare content part is DrawerPrimitive.Content.\nexport { DrawerPrimitive, DrawerPopup as DrawerContent };\n","type":"registry:ui","target":"~/src/siteplane/components/drawer/drawer.tsx"},{"path":"packages/ui/src/components/drawer/drawer.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  /* The panel slides from its edge and the backdrop fades with it, on the\n   * panel time and curve that Sheet shares (rulebook M1). A panel that is let\n   * go after a swipe settles within the release time, scaled by the swipe's\n   * strength; the backdrop follows the finger without a transition. A bottom\n   * panel at rest also eases its margin and padding as its snap point\n   * changes. Zero specificity, so the reduced-motion rules below and a\n   * consumer's transition utility both win. */\n  :where(.sp-drawer-popup) {\n    transition-property: transform, box-shadow, height, background-color;\n    transition-duration: var(--motion-duration-panel);\n    transition-timing-function: var(--motion-ease-panel);\n  }\n\n  :where(\n    .sp-drawer-popup[data-position=\"bottom\"]:not(\n        [data-starting-style],\n        [data-ending-style]\n      )\n  ) {\n    transition-property:\n      transform, box-shadow, height, background-color, margin, padding;\n  }\n\n  :where(.sp-drawer-backdrop) {\n    transition-property: opacity;\n    transition-duration: var(--motion-duration-panel);\n    transition-timing-function: var(--motion-ease-panel);\n  }\n\n  :where(:is(.sp-drawer-popup, .sp-drawer-backdrop)[data-ending-style]) {\n    transition-duration: calc(\n      var(--drawer-swipe-strength) * var(--motion-duration-panel-release)\n    );\n  }\n\n  :where(.sp-drawer-backdrop[data-swiping]) {\n    transition-duration: 0ms;\n  }\n\n  /* Reduced motion opens and closes the panel and its backdrop at once (M5):\n   * the document mode, the provider preset and the system setting. */\n  html[data-motion-reduce=\"true\"] .sp-drawer-popup,\n  html[data-motion-reduce=\"true\"] .sp-drawer-backdrop,\n  [data-sp-motion=\"reduced\"] .sp-drawer-popup,\n  [data-sp-motion=\"reduced\"] .sp-drawer-backdrop {\n    transition-duration: 1ms;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    :root .sp-drawer-popup,\n    :root .sp-drawer-backdrop {\n      transition-duration: 1ms;\n    }\n  }\n\n  /* DrawerMenu touch size (size=\"xl\").\n   *\n   * The shared menu-row recipe in design-rework.css is the compact one: 32px rows\n   * with 14px text and 16px icons, which only a coarse pointer turns into 48px\n   * rows. Navigation and sheets that are tapped ask for the touch size whatever\n   * the pointer: 48px rows, 16px text and a 20px icon slot, as the Sidebar rows at\n   * size xl.\n   *\n   * Every row kind takes the slot through the same two tokens, so the labels of\n   * icon, check, radio, switch and submenu rows still start at the same x\n   * (rulebook I8): the icon width is the width of the check and radio column and\n   * of the slot a switch row keeps, and the Button content gap is the gap of all\n   * of them. The row padding stays 8px, which the menu's negative margin\n   * offsets.\n   *\n   * The extra `[data-slot]` ranks the row rule above the menu-row profile of the\n   * Button, so the two files can load in any order. */\n  :root .sp-drawer-menu[data-size=\"xl\"] {\n    --sp-rw-menu-row-icon: var(--sp-rw-control-icon-xl);\n    --sp-rw-button-content-gap: var(--sp-rw-space-2-5);\n  }\n\n  :root .sp-drawer-menu[data-size=\"xl\"] [data-slot].sp-drawer-menu-item,\n  :root\n    .sp-drawer-menu[data-size=\"xl\"]\n    [data-slot].sp-drawer-menu-checkbox-item,\n  :root .sp-drawer-menu[data-size=\"xl\"] [data-slot].sp-drawer-menu-radio-item,\n  :root .sp-drawer-menu[data-size=\"xl\"] [data-slot].sp-drawer-menu-trigger {\n    min-height: var(--sp-rw-control-xl);\n    border-radius: var(--sp-rw-radius-surface);\n    padding-block: var(--sp-rw-space-3);\n    font-size: var(--sp-rw-font-16);\n    line-height: var(--sp-rw-line-24);\n  }\n\n  :root .sp-drawer-menu[data-size=\"xl\"] .sp-drawer-menu-group-label {\n    padding-block: var(--sp-rw-space-2);\n    font-size: var(--sp-rw-font-13);\n    line-height: var(--sp-rw-line-18);\n  }\n\n  :root .sp-drawer-menu[data-size=\"xl\"] .sp-drawer-menu-separator {\n    margin-block: var(--sp-rw-space-2);\n  }\n\n  /* The current page of a navigation. The row keeps the persistent fill the\n   * Sidebar gives its active row (the pressed glow) and the full foreground, also\n   * under the pointer, so the hover never hides where you are (rulebook T5). */\n  :root\n    .sp-drawer-menu-item[data-chassis][data-variant=\"text\"][aria-current=\"page\"]:not(\n      :disabled,\n      [aria-disabled=\"true\"]\n    ) {\n    background: var(--sp-rw-glow-pressed);\n    color: var(--sp-rw-fg-primary);\n  }\n\n  :root\n    .sp-drawer-menu-item[aria-current=\"page\"]:not(\n      :disabled,\n      [aria-disabled=\"true\"]\n    )\n    svg {\n    color: var(--sp-rw-fg-primary);\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/drawer/drawer.css"},{"path":"packages/ui/src/components/drawer/drawer-menu-shared.tsx","content":"export {\n  menuGroupLabelClassName,\n  menuItemClassName,\n  menuPopupClassName,\n  menuPopupWidthClassNames,\n  menuSeparatorClassName,\n  menuShortcutClassName,\n  menuSubTriggerClassName,\n  type MenuPopupWidth,\n} from \"../menu/menu-shared\";\n","type":"registry:ui","target":"~/src/siteplane/components/drawer/drawer-menu-shared.tsx"},{"path":"packages/ui/src/components/drawer/index.ts","content":"export * from \"./drawer\";\n","type":"registry:ui","target":"~/src/siteplane/components/drawer/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/drawer.md","categories":["Overlays & disclosure"],"meta":{"avoidWhen":["Avoid when swipe dismissal is not wanted; use Sheet.","Avoid for a centered task; use Dialog."],"description":"Swipe-aware modal panel anchored to a viewport edge.","knownLimitations":["Swipe is not the only way to close: keep a visible close action."],"providerNotes":[],"releaseStatus":"stable","searchTerms":["drawer"],"useWhen":["Use for a panel that slides in from a viewport edge and closes with a swipe, such as a mobile menu or a list of actions."]}}