{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sheet",
  "type": "registry:component",
  "title": "Sheet",
  "description": "A panel that rises from an edge of the screen, floating clear of it, and leaves with a swipe.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "cssVars": {
    "light": {
      "info": "oklch(0.623 0.214 259.815)"
    },
    "dark": {
      "info": "oklch(0.623 0.214 259.815)"
    }
  },
  "files": [
    {
      "path": "components/panelui/sheet.tsx",
      "type": "registry:component",
      "target": "@components/panelui/sheet.tsx",
      "content": "'use client';\n\n/**\n * Sheet — a panel that rises over the page from one of its edges, floating\n * clear of them, and leaves with a swipe.\n *\n * ```tsx\n * <Sheet>\n *   <Sheet.Trigger render={<Button />}>Edit profile</Sheet.Trigger>\n *   <Sheet.Content>\n *     <Sheet.Header title=\"Edit profile\" description=\"Shown on your public page.\" />\n *     <Sheet.Body>…</Sheet.Body>\n *     <Sheet.Footer>\n *       <Button>Save</Button>\n *       <Sheet.Close render={<Button variant=\"ghost\" />}>Cancel</Sheet.Close>\n *     </Sheet.Footer>\n *   </Sheet.Content>\n * </Sheet>\n * ```\n *\n * ## It floats\n *\n * A sheet that holds a task for the page is a card laid over it, so by\n * default it stands a few pixels off every edge with all four corners rounded.\n * The corners are large and concentric with the gap around them, which is what\n * makes the card read as belonging to the screen it sits in rather than as a\n * box dropped onto it. `size=\"full\"` or `detached={false}` gives the gap up and\n * meets the edges, for a sheet that needs every pixel.\n *\n * ## Sheets stack\n *\n * A sheet opened from inside another slides over it from the same edge, and\n * the one underneath steps back: a little smaller, pushed inward so its edge\n * still shows, and dimmed. Only the first sheet darkens the page, so two open\n * sheets do not stack two shades of black.\n *\n * The next sheet is declared once, as `Sheet.Next` with a `name`, and opened\n * by any `Sheet.Trigger to=\"name\"` in the sheet: a row in the body, an icon in\n * the header, a button in the footer. The trigger and the sheet it opens are\n * apart in the markup because they are apart on the screen: a list of rows\n * that each open a sheet should not have to wrap every row in one. A trigger\n * can carry a `payload`, so one `Sheet.Next` serves every row of a list.\n *\n * ## The hand decides\n *\n * Every sheet follows a drag and leaves on a flick toward its own edge. The\n * grabber on a top or bottom sheet says so before anybody tries.\n *\n * With a mouse, the text in a sheet is for reading: a press on the title,\n * the description, the body or the footer selects text the way it does on the\n * page, and the sheet is dragged by its grabber and the padding around the\n * header. A finger still swipes the sheet from anywhere.\n *\n * ## The close button\n *\n * Every sheet has a round close button in its top trailing corner. Write a\n * bare `<Sheet.Close />` somewhere in the sheet to put it there instead, for\n * example first in `Sheet.Header` to stand before the title, and the one in\n * the corner steps aside. `showClose={false}` leaves the sheet with none.\n *\n * ## Slides\n *\n * `Sheet.Slides` takes the place of the body with a column of rounded\n * slides, for photos or a deck. By default it is a deck: each slide fills\n * the sheet's height, a turn of the wheel or a swipe of the trackpad glides\n * to the next slide, an arrow key moves one, and a counter under it says\n * which. Without `snap` it is a numbered column of smaller slides that\n * scrolls freely, the way a presentation's slide panel does, and pressing\n * one chooses it.\n */\nimport { Drawer } from '@base-ui/react/drawer';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport {\n  Children,\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n  type CSSProperties,\n  type KeyboardEvent,\n  type ReactElement,\n  type ReactNode,\n} from 'react';\nimport { tv } from 'tailwind-variants';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type { ElementType as RenderElementType } from 'react';\n\n/** Draws a part as another element, or composes it with a component: `render={<a href=\"/\" />}`. */\ntype RenderProp<Tag extends RenderElementType> = { render?: useRender.ComponentProps<Tag>['render'] };\n\n/*\n * One curve for every movement: the sheet arriving, the one behind it stepping\n * back, the page receding. A fast start that eases into place reads as weight,\n * where a symmetric ease reads as a slide show.\n */\nconst MOVE =\n  '[transition:transform_450ms_cubic-bezier(0.32,0.72,0,1),border-radius_450ms_cubic-bezier(0.32,0.72,0,1),margin_450ms_cubic-bezier(0.32,0.72,0,1),height_450ms_cubic-bezier(0.32,0.72,0,1)]';\n\nconst sheetVariants = tv({\n  slots: {\n    backdrop: [\n      'fixed inset-0 z-50 bg-black opacity-[calc(var(--sheet-dim)*(1-var(--drawer-swipe-progress,0)))]',\n      '[--sheet-dim:0.28] dark:[--sheet-dim:0.5]',\n      'transition-opacity duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] data-swiping:duration-0',\n      'data-starting-style:opacity-0 data-ending-style:opacity-0',\n      'data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] motion-reduce:transition-none',\n    ],\n    /*\n     * The viewport holds the gap. The sheet is laid out inside it, so the\n     * distance to every edge is one number, and the corner radius below is\n     * that number's companion.\n     */\n    viewport: 'pointer-events-none fixed inset-0 z-50 flex [--sheet-gap:0.5rem] sm:[--sheet-gap:0.75rem]',\n    popup: [\n      'group/sheet pointer-events-auto relative flex min-h-0 flex-col bg-popover text-popover-foreground outline-none',\n      'shadow-[0_24px_64px_-16px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',\n      MOVE,\n      'data-swiping:select-none data-swiping:duration-0 data-nested-drawer-swiping:duration-0',\n      'data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)]',\n      'motion-reduce:transition-none',\n      /*\n       * The stack. Each sheet behind the front one is 4% smaller and pushed\n       * 14px inward from its edge, so a strip of it shows past the one in front\n       * and the depth can be counted. It scales about its inner edge, so the\n       * strip stays put while it shrinks.\n       */\n      '[--sheet-scale:calc(1-var(--nested-drawers,0)*0.04)] [--sheet-push:calc(var(--nested-drawers,0)*14px)]',\n      // A veil in the sheet's own colour: what is on the sheet behind fades, the\n      // same way in either theme, instead of going grey under a black tint.\n      \"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-popover after:opacity-0 after:transition-opacity after:duration-[450ms] after:content-['']\",\n      'data-nested-drawer-open:after:opacity-50',\n    ],\n    /*\n     * A top or bottom sheet behind another takes the front one's height, so\n     * the strip that shows past it is the same whichever of the two is taller.\n     * A taller sheet behind would otherwise stand above the stack, and a\n     * shorter one would vanish behind the front one entirely.\n     */\n    stacked: [\n      'data-nested-drawer-open:h-(--drawer-frontmost-height) data-nested-drawer-open:overflow-hidden',\n      // Lets the height animate to and from `auto` where the browser can.\n      '[interpolate-size:allow-keywords]',\n    ],\n    grabber: [\n      'pointer-events-none absolute inset-x-0 z-10 mx-auto flex h-6 w-24 justify-center',\n      'before:h-[5px] before:w-9 before:rounded-full before:bg-muted-foreground/35',\n    ],\n    close: [\n      'relative inline-flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-full',\n      'bg-muted text-muted-foreground outline-none transition-[background-color,color,scale] duration-150',\n      'after:absolute after:-inset-1.5 hover:bg-foreground/10 hover:text-foreground active:scale-95',\n      'focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none',\n    ],\n    closeCorner: 'absolute end-4 top-4 z-10',\n    /*\n     * A close button written into the header shares the title's line: the\n     * header becomes three columns, the button takes the first or the last\n     * depending on where it was written, and the text keeps the middle.\n     */\n    header: [\n      'flex shrink-0 flex-col gap-1 px-6 pt-6 pb-3',\n      'has-[>[data-slot=sheet-close]]:grid has-[>[data-slot=sheet-close]]:grid-cols-[auto_minmax(0,1fr)_auto] has-[>[data-slot=sheet-close]]:gap-x-3',\n      '[&>*]:col-start-2',\n      '[&>[data-slot=sheet-close]]:row-span-2 [&>[data-slot=sheet-close]]:row-start-1 [&>[data-slot=sheet-close]]:-my-1 [&>[data-slot=sheet-close]]:self-start',\n      '[&>[data-slot=sheet-close]:first-child]:col-start-1 [&>[data-slot=sheet-close]:not(:first-child)]:col-start-3',\n    ],\n    title: 'text-[19px] font-semibold leading-6 tracking-[-0.01em]',\n    description: 'text-[15px] leading-5 text-muted-foreground',\n    body: 'relative flex min-h-0 flex-1 flex-col',\n    bodyViewport: 'min-h-0 flex-1 overscroll-contain outline-none',\n    bodyContent: 'px-6 pb-2',\n    scrollbar:\n      'my-3 me-1 flex w-1.5 opacity-0 transition-opacity delay-300 data-hovering:opacity-100 data-hovering:delay-0 data-scrolling:opacity-100 data-scrolling:delay-0',\n    thumb: 'relative flex-1 rounded-full bg-foreground/20',\n    footer: 'flex shrink-0 flex-col gap-2 px-6 pt-3 pb-6',\n    /*\n     * Slides. As a deck the viewport is a size container, so a slide can be\n     * exactly its height less the padding, which is what lets one flick land\n     * on one slide. The minimum height is for a sheet that sizes itself to\n     * its content, where a deck would otherwise have no height to fill.\n     */\n    slides: 'relative flex min-h-0 flex-1 flex-col',\n    // Focus is shown on the current slide, not as a square round the scroller.\n    slidesViewport: 'group/slides min-h-0 flex-1 overscroll-contain outline-none',\n    slidesContent: 'flex flex-col gap-3 px-3',\n    // 20px is the sheet's 32 less the 12px it is set in by: one corner, two curves.\n    slide: [\n      'relative shrink-0 overflow-hidden rounded-[20px] bg-muted',\n      '[&>img]:size-full [&>img]:object-cover [&>video]:size-full [&>video]:object-cover',\n      // A hairline over the picture, so a photo keeps an edge on a sheet of its own colour.\n      \"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:ring-1 after:ring-inset after:ring-foreground/[0.08] after:content-['']\",\n      'group-focus-visible/slides:data-current:ring-2 group-focus-visible/slides:data-current:ring-ring',\n    ],\n    slideRow: 'grid grid-cols-[1.25rem_minmax(0,1fr)] items-start gap-2.5',\n    slideNumber: 'pt-1 text-end text-xs font-medium tabular-nums text-muted-foreground',\n    slideButton: [\n      'group/slide block w-full cursor-pointer rounded-[14px] text-start outline-none transition-shadow duration-150',\n      'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-popover',\n    ],\n    // No blur behind it: a filter over a scrolling picture repaints on every frame.\n    counter:\n      'pointer-events-none absolute inset-x-0 bottom-5 mx-auto w-fit rounded-full bg-black/60 px-2.5 py-1 text-xs font-medium tabular-nums text-white',\n    /*\n     * The page behind steps back by 3%, not more: enough to read as a layer\n     * underneath, not so much that the page looks like it shrank. It keeps its\n     * own colours and draws no outline of its own: the only edge is the strip\n     * of the surface it steps back onto, which is the muted tone of the theme\n     * rather than black, so it is quiet in light and dark alike. A rim on top\n     * of that strip doubles every border the page sits inside.\n     *\n     * It shrinks about its centre, so the strip is the same width on every\n     * side, and its corners take `--stage-radius`: set that to the radius of\n     * whatever the stage sits in, less the strip, and the two outlines are\n     * concentric instead of a second, tighter curve inside the first.\n     */\n    stage: 'relative isolate overflow-hidden [--stage-radius:1rem]',\n    stageBackground: 'absolute inset-0 -z-10 bg-muted',\n    stagePage: [\n      'relative h-full origin-center bg-background will-change-transform',\n      'transition-[transform,border-radius] duration-[450ms] ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none',\n      '[--stage-progress:calc(1-var(--drawer-swipe-progress,0))]',\n      'data-active:[transform:scale(calc(1-0.03*var(--stage-progress)))]',\n      'data-active:overflow-hidden data-active:rounded-[calc(var(--stage-radius)*var(--stage-progress))]',\n    ],\n  },\n  variants: {\n    side: {\n      bottom: {\n        grabber: 'top-0 items-start pt-1.5',\n        viewport:\n          'items-end justify-center px-(--sheet-gap) pt-12 pb-[max(var(--sheet-gap),env(safe-area-inset-bottom))]',\n        popup: [\n          'max-h-full w-full max-w-lg origin-[50%_0] rounded-[32px]',\n          '[transform:translateY(calc(var(--drawer-swipe-movement-y,0px)-var(--sheet-push)))_scale(var(--sheet-scale))]',\n          'data-starting-style:[transform:translateY(calc(100%+var(--sheet-gap)+2rem))]',\n          'data-ending-style:[transform:translateY(calc(100%+var(--sheet-gap)+2rem))]',\n        ],\n      },\n      top: {\n        // Swiped up, so the grabber sits on the edge the hand pushes.\n        grabber: 'bottom-0 items-end pb-1.5',\n        viewport:\n          'items-start justify-center px-(--sheet-gap) pb-12 pt-[max(var(--sheet-gap),env(safe-area-inset-top))]',\n        popup: [\n          'max-h-full w-full max-w-lg origin-[50%_100%] rounded-[32px]',\n          '[transform:translateY(calc(var(--drawer-swipe-movement-y,0px)+var(--sheet-push)))_scale(var(--sheet-scale))]',\n          'data-starting-style:[transform:translateY(calc(-100%-var(--sheet-gap)-2rem))]',\n          'data-ending-style:[transform:translateY(calc(-100%-var(--sheet-gap)-2rem))]',\n        ],\n      },\n      left: {\n        viewport: 'items-stretch justify-start p-(--sheet-gap)',\n        popup: [\n          'h-full w-[min(24rem,calc(100vw-2*var(--sheet-gap)))] origin-[100%_50%] rounded-[28px]',\n          '[transform:translateX(calc(var(--drawer-swipe-movement-x,0px)+var(--sheet-push)))_scale(var(--sheet-scale))]',\n          'data-starting-style:[transform:translateX(calc(-100%-var(--sheet-gap)-2rem))]',\n          'data-ending-style:[transform:translateX(calc(-100%-var(--sheet-gap)-2rem))]',\n        ],\n      },\n      right: {\n        viewport: 'items-stretch justify-end p-(--sheet-gap)',\n        popup: [\n          'h-full w-[min(24rem,calc(100vw-2*var(--sheet-gap)))] origin-[0_50%] rounded-[28px]',\n          '[transform:translateX(calc(var(--drawer-swipe-movement-x,0px)-var(--sheet-push)))_scale(var(--sheet-scale))]',\n          'data-starting-style:[transform:translateX(calc(100%+var(--sheet-gap)+2rem))]',\n          'data-ending-style:[transform:translateX(calc(100%+var(--sheet-gap)+2rem))]',\n        ],\n      },\n    },\n    size: {\n      auto: {},\n      half: { popup: 'h-[min(50dvh,100%)]' },\n      full: { popup: 'h-full' },\n    },\n    // Meets the edges it rises from instead of standing clear of them.\n    attached: {\n      true: {},\n    },\n    blur: {\n      true: { backdrop: 'backdrop-blur-sm [--sheet-dim:0.16] dark:[--sheet-dim:0.36]' },\n    },\n  },\n  compoundVariants: [\n    {\n      side: 'bottom',\n      attached: true,\n      class: {\n        popup:\n          '-mx-(--sheet-gap) -mb-[max(var(--sheet-gap),env(safe-area-inset-bottom))] max-w-[calc(100%+2*var(--sheet-gap))] rounded-b-none pb-[env(safe-area-inset-bottom)] sm:mx-0 sm:max-w-2xl',\n      },\n    },\n    {\n      side: 'top',\n      attached: true,\n      class: {\n        popup:\n          '-mx-(--sheet-gap) -mt-[max(var(--sheet-gap),env(safe-area-inset-top))] max-w-[calc(100%+2*var(--sheet-gap))] rounded-t-none sm:mx-0 sm:max-w-2xl',\n      },\n    },\n    { side: 'left', attached: true, class: { popup: '-my-(--sheet-gap) -ms-(--sheet-gap) rounded-s-none' } },\n    { side: 'right', attached: true, class: { popup: '-my-(--sheet-gap) -me-(--sheet-gap) rounded-e-none' } },\n  ],\n  defaultVariants: { side: 'bottom', size: 'auto' },\n});\n\n/** Where the sheet rises from, which is also the direction it leaves in. */\nexport type SheetSide = 'bottom' | 'top' | 'left' | 'right';\n\nconst SWIPE: Record<SheetSide, 'down' | 'up' | 'left' | 'right'> = {\n  bottom: 'down',\n  top: 'up',\n  left: 'left',\n  right: 'right',\n};\n\n/**\n * The sheets declared with `Sheet.Next` in one stack, by name. One map serves\n * the whole stack, so a trigger finds its sheet wherever each of them sits.\n */\ninterface SheetStack {\n  handle(name: string): DrawerHandle<unknown>;\n}\n\ntype DrawerHandle<Payload> = ReturnType<typeof Drawer.createHandle<Payload>>;\n\nfunction createStack(): SheetStack {\n  const handles = new Map<string, DrawerHandle<unknown>>();\n  return {\n    handle(name) {\n      let handle = handles.get(name);\n      if (!handle) {\n        handle = Drawer.createHandle<unknown>();\n        handles.set(name, handle);\n      }\n      return handle;\n    },\n  };\n}\n\ninterface SheetContextValue {\n  side: SheetSide;\n  stack: SheetStack;\n  /** Opened from inside another sheet, which already darkens the page. */\n  nested: boolean;\n  /** Set by `Sheet.Content`. */\n  inline: boolean;\n  /** Whether a swipe can close it, which is what the grabber offers. */\n  dismissible: boolean;\n  /** Whether the round close button stands in the corner. */\n  showClose: boolean;\n  /**\n   * Called by a bare `Sheet.Close` written into the sheet, which takes the\n   * corner button's place. Returns the undo.\n   */\n  claimClose(): () => void;\n  /** Lets a sheet opened from this one be closed with it. Returns the undo. */\n  register(child: SheetHandle): () => void;\n  /** Closes this sheet and every sheet opened from it. */\n  closeTree(): void;\n  /** Whether a sheet opened from this one is open. */\n  hasOpenChild(): boolean;\n}\n\ninterface SheetHandle {\n  isOpen(): boolean;\n  closeTree(): void;\n}\n\nconst SheetContext = createContext<SheetContextValue | null>(null);\n\nfunction useSheet(part: string): SheetContextValue {\n  const context = useContext(SheetContext);\n  if (!context) throw new Error(`${part} must be used inside a <Sheet>.`);\n  return context;\n}\n\nexport interface SheetProps {\n  children?: ReactNode;\n  /** Whether the sheet is open, when the caller holds that. */\n  open?: boolean;\n  /** Called when the sheet asks to open or close. */\n  onOpenChange?: (open: boolean) => void;\n  /**\n   * Called once it has finished opening or closing, after its transition has\n   * ended. `false` means the exit is over, so a sheet mounted from state can\n   * be unmounted.\n   */\n  onOpenChangeComplete?: (open: boolean) => void;\n  /** Whether it starts open, when the caller does not hold it. */\n  defaultOpen?: boolean;\n  /** The edge it rises from and is swiped back to. Default `bottom`. */\n  side?: SheetSide;\n  /**\n   * Whether a click outside, a swipe or Escape closes it. Turn it off for a\n   * sheet that must be answered; `Sheet.Close` still works.\n   */\n  dismissible?: boolean;\n}\n\nfunction SheetRoot(props: SheetProps) {\n  return <SheetFrame {...props} />;\n}\nSheetRoot.displayName = 'Sheet';\n\ninterface SheetFrameProps extends Omit<SheetProps, 'children'> {\n  children?: ReactNode | ((payload: unknown) => ReactNode);\n  /** Set by `Sheet.Next`: the handle its triggers open it through. */\n  handle?: DrawerHandle<unknown>;\n}\n\nfunction SheetFrame({\n  children,\n  open,\n  onOpenChange,\n  onOpenChangeComplete,\n  defaultOpen = false,\n  side = 'bottom',\n  dismissible = true,\n  handle: drawerHandle,\n}: SheetFrameProps) {\n  // A sheet inside another sheet's content finds that sheet's context.\n  const parent = useContext(SheetContext);\n  const nested = parent !== null && !parent.inline;\n  const [ownStack] = useState(createStack);\n  const stack = nested && parent ? parent.stack : ownStack;\n  // Held here as well as by the primitive, so a sheet opened from this one\n  // can close it.\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const isOpen = open ?? internalOpen;\n  const setOpen = useCallback(\n    (next: boolean) => {\n      if (open === undefined) setInternalOpen(next);\n      onOpenChange?.(next);\n    },\n    [open, onOpenChange]\n  );\n  // The sheets opened from this one, so a press on the page behind them all\n  // can close the whole stack. The primitive gives that press to no one\n  // while a second sheet is open.\n  const opened = useRef(new Set<SheetHandle>());\n  const live = useRef({ isOpen, setOpen });\n  live.current = { isOpen, setOpen };\n  const handle = useMemo<SheetHandle>(\n    () => ({\n      isOpen: () => live.current.isOpen,\n      closeTree: () => {\n        opened.current.forEach((child) => child.closeTree());\n        if (live.current.isOpen) live.current.setOpen(false);\n      },\n    }),\n    []\n  );\n  useEffect(() => (nested && parent ? parent.register(handle) : undefined), [nested, parent, handle]);\n\n  const context = useMemo<SheetContextValue>(\n    () => ({\n      side,\n      stack,\n      nested,\n      inline: false,\n      dismissible,\n      showClose: true,\n      claimClose: () => () => {},\n      register: (child) => {\n        opened.current.add(child);\n        return () => opened.current.delete(child);\n      },\n      closeTree: handle.closeTree,\n      hasOpenChild: () => [...opened.current].some((child) => child.isOpen()),\n    }),\n    [side, stack, nested, dismissible, handle]\n  );\n\n  return (\n    <SheetContext.Provider value={context}>\n      <Drawer.Root\n        open={isOpen}\n        handle={drawerHandle}\n        swipeDirection={SWIPE[side]}\n        disablePointerDismissal={!dismissible}\n        onOpenChangeComplete={onOpenChangeComplete}\n        onOpenChange={(next, details) => {\n          // The primitive has a switch for a press outside and none for\n          // Escape or a swipe, so those are refused here.\n          if (!next && !dismissible && details.reason !== 'close-press' && details.reason !== 'imperative-action') {\n            return;\n          }\n          setOpen(next);\n        }}\n      >\n        {typeof children === 'function' ? ({ payload }: { payload: unknown }) => children(payload) : children}\n      </Drawer.Root>\n    </SheetContext.Provider>\n  );\n}\n\nexport interface SheetTriggerProps extends Omit<ComponentProps<'button'>, 'children'> {\n  children?: ReactNode;\n  /**\n   * Your own element to be the trigger, such as a styled button. It is given\n   * the click handler and the attributes that tie it to the sheet.\n   */\n  render?: ReactElement;\n  /**\n   * The `name` of a `Sheet.Next` to open instead of the sheet this trigger is\n   * in. Any button in a sheet can open the next one this way, wherever it sits.\n   */\n  to?: string;\n  /**\n   * Handed to the `Sheet.Next` that `to` names, as the argument of its\n   * children function. Use it to open one sheet for every row of a list.\n   */\n  payload?: unknown;\n}\n\n/**\n * What opens a sheet: its own, or with `to`, the `Sheet.Next` of that name.\n * A plain button unless `render` supplies one.\n */\nfunction SheetTrigger({ render, children, to, payload, ...props }: SheetTriggerProps) {\n  const sheet = useContext(SheetContext);\n  if (to !== undefined) {\n    if (!sheet) throw new Error('Sheet.Trigger with `to` must be used inside a <Sheet>.');\n    return (\n      <Drawer.Trigger handle={sheet.stack.handle(to)} payload={payload} render={render} {...props}>\n        {children}\n      </Drawer.Trigger>\n    );\n  }\n  return (\n    <Drawer.Trigger render={render} {...props}>\n      {children}\n    </Drawer.Trigger>\n  );\n}\nSheetTrigger.displayName = 'Sheet.Trigger';\n\nexport interface SheetNextProps extends Omit<SheetContentProps, 'children' | 'inline'> {\n  /** What a `Sheet.Trigger` passes as `to` to open this sheet. Unique within one stack of sheets. */\n  name: string;\n  /**\n   * The sheet's parts. Pass a function to read the `payload` of the trigger\n   * that opened it; it is `undefined` when that trigger has none.\n   */\n  children?: ReactNode | ((payload: any) => ReactNode);\n  /** The edge it rises from. Default: the edge of the sheet it is declared in. */\n  side?: SheetSide;\n  /** Whether a click outside, a swipe or Escape closes it. Default true. */\n  dismissible?: boolean;\n  /** Called when it opens or closes. */\n  onOpenChange?: (open: boolean) => void;\n  /**\n   * Called once it has finished opening or closing, after its transition has\n   * ended. `false` means the exit is over, so a sheet mounted from state can\n   * be unmounted.\n   */\n  onOpenChangeComplete?: (open: boolean) => void;\n}\n\n/**\n * A sheet opened from the one it is declared in, by any `Sheet.Trigger` whose\n * `to` is its `name`. Put it anywhere inside the first sheet's content; it\n * takes `Sheet.Content`'s props and draws its own content, so its children are\n * the header, body and footer.\n */\nfunction SheetNext({\n  name,\n  children,\n  side,\n  dismissible = true,\n  onOpenChange,\n  onOpenChangeComplete,\n  ...content\n}: SheetNextProps) {\n  const parent = useSheet('Sheet.Next');\n  return (\n    <SheetFrame\n      handle={parent.stack.handle(name)}\n      side={side ?? parent.side}\n      dismissible={dismissible}\n      onOpenChange={onOpenChange}\n      onOpenChangeComplete={onOpenChangeComplete}\n    >\n      {(payload) => (\n        <SheetContent {...content}>\n          {typeof children === 'function' ? <NextParts render={children} payload={payload} /> : children}\n        </SheetContent>\n      )}\n    </SheetFrame>\n  );\n}\nSheetNext.displayName = 'Sheet.Next';\n\n/**\n * Calls a `Sheet.Next` children function. As a component of its own it only\n * runs once the sheet is open and its content mounted, which is when the\n * trigger has handed over its payload.\n */\nfunction NextParts({ render, payload }: { render: (payload: any) => ReactNode; payload: unknown }) {\n  return <>{render(payload)}</>;\n}\n\nexport interface SheetContentProps extends Omit<ComponentProps<'div'>, 'children'> {\n  children?: ReactNode;\n  /**\n   * Stand clear of the screen edges with every corner rounded. On by default;\n   * off, the sheet meets the edges it rises from.\n   */\n  detached?: boolean;\n  /**\n   * How tall a top or bottom sheet is. `auto` fits the content; `half` and\n   * `full` take that share of the screen, and `full` meets the edges.\n   */\n  size?: 'auto' | 'half' | 'full';\n  /**\n   * Show the grabber. On by default for a top or bottom sheet that a swipe\n   * can close, and off when `dismissible` is.\n   */\n  showGrabber?: boolean;\n  /**\n   * Show the round close button in the top trailing corner. Default true.\n   * Write a bare `<Sheet.Close />` into the sheet to place it somewhere else,\n   * or set this to `false` for a sheet with no close button at all.\n   */\n  showClose?: boolean;\n  /** Frost the page behind the sheet instead of only dimming it. */\n  blur?: boolean;\n  /**\n   * Draw the sheet in the page instead of over it: no backdrop, no trigger,\n   * always there. For previews and for a layout that shows the sheet as part\n   * of a screen.\n   */\n  inline?: boolean;\n}\n\n/** The sheet itself, with the backdrop behind it. */\nfunction SheetContent({\n  children,\n  detached = true,\n  size = 'auto',\n  showGrabber,\n  showClose = true,\n  blur = false,\n  inline = false,\n  className,\n  ...props\n}: SheetContentProps) {\n  const sheet = useSheet('Sheet.Content');\n  const vertical = sheet.side === 'bottom' || sheet.side === 'top';\n  const slots = sheetVariants({\n    side: sheet.side,\n    size: vertical ? size : undefined,\n    attached: !detached || (vertical && size === 'full'),\n    blur,\n  });\n  // Bare close buttons written into the sheet. While there is one, the\n  // corner button is not drawn.\n  const [placed, setPlaced] = useState(0);\n  const claimClose = useCallback(() => {\n    setPlaced((count) => count + 1);\n    return () => setPlaced((count) => count - 1);\n  }, []);\n  const corner = showClose && placed === 0;\n  const context = useMemo(\n    () => ({ ...sheet, inline, showClose: corner, claimClose }),\n    [sheet, inline, corner, claimClose]\n  );\n\n  const inside = (\n    <>\n      {(showGrabber ?? (vertical && sheet.dismissible)) ? <div aria-hidden className={slots.grabber()} /> : null}\n      {corner ? <CloseButton className={slots.closeCorner()} /> : null}\n      {children}\n    </>\n  );\n\n  if (inline) {\n    return (\n      <SheetContext.Provider value={context}>\n        <div className={slots.popup({ className: ['[transform:none]', className] })} {...props}>\n          {inside}\n        </div>\n      </SheetContext.Provider>\n    );\n  }\n\n  return (\n    <Drawer.Portal>\n      {/* The first sheet darkens the page; one opened from it does not add a second shade. */}\n      {sheet.nested ? null : (\n        <Drawer.Backdrop\n          className={slots.backdrop()}\n          // With a second sheet open, a press here is outside both, and closes both.\n          onClick={() => {\n            if (sheet.hasOpenChild()) sheet.closeTree();\n          }}\n        />\n      )}\n      <Drawer.Viewport className={slots.viewport()}>\n        <Drawer.Popup className={slots.popup({ className: [vertical && slots.stacked(), className] })} {...props}>\n          {/* The popup is portalled out from under the provider, so the\n              context is given again for the parts inside it. */}\n          <SheetContext.Provider value={context}>{inside}</SheetContext.Provider>\n        </Drawer.Popup>\n      </Drawer.Viewport>\n    </Drawer.Portal>\n  );\n}\nSheetContent.displayName = 'Sheet.Content';\n\nexport interface SheetHeaderProps extends Omit<ComponentProps<'div'>, 'title'> {\n  /** The heading. In an open sheet it is also what the sheet is announced as. */\n  title?: ReactNode;\n  /** A line under the heading. */\n  description?: ReactNode;\n}\n\n/**\n * The heading block. It leaves room for the close button beside the title,\n * so the two share the top line instead of stacking.\n */\nfunction SheetHeader({\n  title,\n  description,\n  className,\n  children,\n  render,\n  ...props\n}: SheetHeaderProps & RenderProp<'div'>) {\n  const { showClose } = useSheet('Sheet.Header');\n  const slots = sheetVariants();\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        className: slots.header({ className: [showClose && 'pe-14', className] }),\n        children: (\n          <>\n            {title !== undefined ? <SheetTitle>{title}</SheetTitle> : null}\n            {description !== undefined ? <SheetDescription>{description}</SheetDescription> : null}\n            {children}\n          </>\n        ),\n      },\n      props\n    ),\n  });\n}\nSheetHeader.displayName = 'Sheet.Header';\n\nexport interface SheetTitleProps extends ComponentProps<'h2'> {}\n\n/** The sheet's name, announced when it opens. */\nfunction SheetTitle({ className, ...props }: SheetTitleProps) {\n  const { inline } = useSheet('Sheet.Title');\n  const slots = sheetVariants();\n  if (inline) return <h2 className={slots.title({ className })} {...props} />;\n  return <Drawer.Content render={<Drawer.Title className={slots.title({ className })} {...props} />} />;\n}\nSheetTitle.displayName = 'Sheet.Title';\n\nexport interface SheetDescriptionProps extends ComponentProps<'p'> {}\n\n/** A sentence under the title, read out after it. */\nfunction SheetDescription({ className, ...props }: SheetDescriptionProps) {\n  const { inline } = useSheet('Sheet.Description');\n  const slots = sheetVariants();\n  if (inline) return <p className={slots.description({ className })} {...props} />;\n  return <Drawer.Content render={<Drawer.Description className={slots.description({ className })} {...props} />} />;\n}\nSheetDescription.displayName = 'Sheet.Description';\n\nexport interface SheetBodyProps extends ComponentProps<'div'> {}\n\n/**\n * The part that scrolls. The header and footer stay put while it moves, and\n * the scrollbar is a thin drawn thumb that shows while the content is moving.\n */\nfunction SheetBody({ className, children, ...props }: SheetBodyProps) {\n  const { inline } = useSheet('Sheet.Body');\n  const slots = sheetVariants();\n  const content = (\n    <ScrollArea.Content className={slots.bodyContent({ className })} {...props}>\n      {children}\n    </ScrollArea.Content>\n  );\n  return (\n    <ScrollArea.Root className={slots.body()}>\n      <ScrollArea.Viewport className={slots.bodyViewport()}>\n        {inline ? content : <Drawer.Content render={content} />}\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar orientation=\"vertical\" className={slots.scrollbar()}>\n        <ScrollArea.Thumb className={slots.thumb()} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\nSheetBody.displayName = 'Sheet.Body';\n\nexport interface SheetFooterProps extends ComponentProps<'div'> {}\n\n/** The actions, stacked full width at the bottom of the sheet. */\nfunction SheetFooter({ className, ...props }: SheetFooterProps) {\n  const { inline } = useSheet('Sheet.Footer');\n  const slots = sheetVariants();\n  const footer = <div className={slots.footer({ className })} {...props} />;\n  return inline ? footer : <Drawer.Content render={footer} />;\n}\nSheetFooter.displayName = 'Sheet.Footer';\n\nexport interface SheetCloseProps extends ComponentProps<'button'> {\n  /**\n   * Your own element to close the sheet with, such as a Cancel button.\n   * Without it, and without children, this is the round close button, drawn\n   * where it is written in place of the one in the corner.\n   */\n  render?: ReactElement;\n  /** Draw the round close button. A `Sheet.Close` with nothing inside it already is one. */\n  icon?: boolean;\n}\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\n/**\n * Closes the sheet. Works even when `dismissible` is off. Written bare, it is\n * the round close button, and the sheet stops drawing its own in the corner.\n */\nfunction SheetClose({ render, icon = false, children, ...props }: SheetCloseProps) {\n  const sheet = useSheet('Sheet.Close');\n  const bare = icon || (render === undefined && children === undefined);\n  const { claimClose } = sheet;\n  useIsomorphicLayoutEffect(() => (bare ? claimClose() : undefined), [bare, claimClose]);\n  if (bare) return <CloseButton {...props} />;\n  if (sheet.inline) {\n    return (\n      <button type=\"button\" data-slot=\"sheet-close\" {...props}>\n        {children}\n      </button>\n    );\n  }\n  return (\n    <Drawer.Close data-slot=\"sheet-close\" render={render} {...props}>\n      {children}\n    </Drawer.Close>\n  );\n}\nSheetClose.displayName = 'Sheet.Close';\n\n/** The round button with a cross in it. */\nfunction CloseButton({ className, 'aria-label': label = 'Close', ...props }: ComponentProps<'button'>) {\n  const { inline } = useSheet('Sheet.Close');\n  const slots = sheetVariants();\n  const glyph = (\n    <svg\n      viewBox=\"0 0 24 24\"\n      aria-hidden\n      className=\"size-4\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2.4}\n      strokeLinecap=\"round\"\n    >\n      <path d=\"M6 6l12 12M18 6L6 18\" />\n    </svg>\n  );\n  const shared = { 'data-slot': 'sheet-close', 'aria-label': label, className: slots.close({ className }), ...props };\n  if (inline) {\n    return (\n      <button type=\"button\" {...shared}>\n        {glyph}\n      </button>\n    );\n  }\n  return <Drawer.Close {...shared}>{glyph}</Drawer.Close>;\n}\n\n/* ----------------------------------------------------------------- slides */\n\ninterface SlidesContextValue {\n  snap: boolean;\n  count: number;\n  current: number;\n  choose(index: number): void;\n  /** Set when pressing a slide chooses it. */\n  pressable: boolean;\n}\n\nconst SlidesContext = createContext<SlidesContextValue | null>(null);\n\n/** The rounded frame of a slide: the slide itself in a deck, inside its row in a column. */\nconst frameOf = (slide: HTMLElement) => slide.querySelector<HTMLElement>('[data-slide-frame]') ?? slide;\nconst SlideIndexContext = createContext(0);\n\nfunction reduceMotion() {\n  return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nexport interface SheetSlidesProps extends Omit<ComponentProps<'div'>, 'onChange'> {\n  /**\n   * `true` makes it a deck: each slide fills the height, and a scroll\n   * glides to the next one. `false` makes it a numbered column of smaller\n   * slides that scrolls freely. Default `true`.\n   */\n  snap?: boolean;\n  /** The current slide, counted from 0, when the caller holds it. Setting it scrolls that slide into view. */\n  index?: number;\n  /** The slide to start on, when the caller does not hold it. Default `0`. */\n  defaultIndex?: number;\n  /**\n   * Called with the new index when the current slide changes: scrolled to\n   * in a deck, pressed in a column. In a column, passing it makes each\n   * slide a button.\n   */\n  onIndexChange?: (index: number) => void;\n  /** Show \"3 / 8\" under the current slide. Default: on in a deck. */\n  counter?: boolean;\n  /** The shape of a slide in a column, as a CSS aspect ratio. Default `16 / 9`. */\n  aspect?: string;\n}\n\n/**\n * A column of rounded slides in place of `Sheet.Body`: a deck that moves one\n * slide at a time, or with `snap={false}`, a numbered column to choose from.\n */\nfunction SheetSlides({\n  snap = true,\n  index,\n  defaultIndex = 0,\n  onIndexChange,\n  counter,\n  aspect = '16 / 9',\n  className,\n  style,\n  children,\n  'aria-label': label = 'Slides',\n  ...props\n}: SheetSlidesProps) {\n  const slots = sheetVariants();\n  const viewport = useRef<HTMLDivElement>(null);\n  const slides = Children.toArray(children);\n  const count = slides.length;\n  const [internal, setInternal] = useState(defaultIndex);\n  const current = Math.min(Math.max(index ?? internal, 0), Math.max(count - 1, 0));\n  const live = useRef({ current, onIndexChange, controlled: index !== undefined });\n  live.current = { current, onIndexChange, controlled: index !== undefined };\n\n  const slideAt = (at: number) =>\n    viewport.current?.querySelectorAll<HTMLElement>('[data-slot=sheet-slide]')[at] ?? null;\n  // Scrolls the slides alone. `scrollIntoView` would move every scroller\n  // above them too, the page included when a sheet is drawn inline.\n  const reveal = useCallback(\n    (at: number, smooth: boolean) => {\n      const root = viewport.current;\n      const slide = slideAt(at);\n      if (!root || !slide) return;\n      const box = root.getBoundingClientRect();\n      const frame = frameOf(slide).getBoundingClientRect();\n      const above = frame.top - box.top;\n      const below = frame.bottom - box.bottom;\n      const by = snap ? above - (box.height - frame.height) / 2 : above < 0 ? above - 8 : below > 0 ? below + 8 : 0;\n      if (by === 0) return;\n      const behavior = smooth && !reduceMotion() ? 'smooth' : 'auto';\n      if (!snap) {\n        root.scrollBy({ top: by, behavior });\n        return;\n      }\n      // A deck stops at every slide on the way, which is right for a hand\n      // and wrong for Home or End. Snapping is off for the length of a jump.\n      root.style.scrollSnapType = 'none';\n      root.scrollBy({ top: by, behavior });\n      let restored = false;\n      const restore = () => {\n        if (restored) return;\n        restored = true;\n        root.style.scrollSnapType = '';\n      };\n      if (behavior === 'smooth' && 'onscrollend' in window) {\n        root.addEventListener('scrollend', restore, { once: true });\n        window.setTimeout(restore, 1200);\n      } else {\n        requestAnimationFrame(restore);\n      }\n    },\n    [snap]\n  );\n\n  // The slide last brought into view. One that became current by being\n  // scrolled to is already in view, and must not be scrolled to again: that\n  // would pull a deck back to it in the middle of a longer scroll.\n  const shown = useRef<number | null>(null);\n\n  const settle = useCallback((next: number) => {\n    shown.current = next;\n    if (next === live.current.current) return;\n    if (!live.current.controlled) setInternal(next);\n    live.current.onIndexChange?.(next);\n  }, []);\n\n  // A deck's current slide is the one most in view.\n  useEffect(() => {\n    const root = viewport.current;\n    if (!snap || !root || typeof IntersectionObserver === 'undefined') return;\n    const observer = new IntersectionObserver(\n      (entries) => {\n        for (const entry of entries) {\n          if (entry.isIntersecting) settle(Number((entry.target as HTMLElement).dataset.index));\n        }\n      },\n      { root, threshold: 0.6 }\n    );\n    root.querySelectorAll('[data-slot=sheet-slide]').forEach((slide) => observer.observe(slide));\n    return () => observer.disconnect();\n  }, [snap, count, settle]);\n\n  // Opens on the current slide, and follows `index` when it is set from outside.\n  useEffect(() => {\n    const first = shown.current === null;\n    if (!first && shown.current === current) return;\n    shown.current = current;\n    if (first && current === 0) return;\n    const box = viewport.current?.getBoundingClientRect();\n    const slide = slideAt(current);\n    const frame = slide ? frameOf(slide).getBoundingClientRect() : undefined;\n    const visible = box && frame && frame.top >= box.top - 1 && frame.bottom <= box.bottom + 1;\n    // A deck that scrolled there by itself is already on it.\n    if (!visible || (first && snap)) reveal(current, !first);\n  }, [current, reveal, snap]);\n\n  const go = (next: number) => {\n    const at = Math.min(Math.max(next, 0), count - 1);\n    if (snap) reveal(at, true);\n    else {\n      settle(at);\n      reveal(at, true);\n      slideAt(at)?.querySelector<HTMLElement>('button')?.focus();\n    }\n  };\n  const goNow = useRef(go);\n  goNow.current = go;\n\n  /*\n   * A wheel or a trackpad turns a deck one slide per gesture. Left to the\n   * browser, a slow scroll drifts part of the way, eases off and springs\n   * back, and a fast one runs past several slides. Here a deliberate scroll\n   * glides to the next slide, and the rest of the same gesture, the\n   * trackpad's momentum included, is absorbed rather than fought. A touch\n   * screen keeps the browser's own snapping, which follows the finger.\n   */\n  useEffect(() => {\n    const root = viewport.current;\n    if (!snap || !root) return;\n    let pending = 0;\n    let lastWheel = 0;\n    let lockedUntil = 0;\n    const onWheel = (event: WheelEvent) => {\n      // Sideways scrolling and pinch-zoom are not turns.\n      if (event.ctrlKey || Math.abs(event.deltaX) > Math.abs(event.deltaY)) return;\n      event.preventDefault();\n      const now = performance.now();\n      const gap = now - lastWheel;\n      lastWheel = now;\n      if (now < lockedUntil) {\n        // Still the same gesture: keep it from turning a second slide.\n        lockedUntil = Math.max(lockedUntil, now + 140);\n        return;\n      }\n      if (gap > 200) pending = 0;\n      pending += event.deltaMode === 1 ? event.deltaY * 16 : event.deltaY;\n      if (Math.abs(pending) < 24) return;\n      const step = Math.sign(pending);\n      pending = 0;\n      lockedUntil = now + 500;\n      goNow.current(live.current.current + step);\n    };\n    root.addEventListener('wheel', onWheel, { passive: false });\n    return () => root.removeEventListener('wheel', onWheel);\n  }, [snap]);\n\n  const keys = (event: KeyboardEvent<HTMLDivElement>) => {\n    const step =\n      event.key === 'ArrowDown' || event.key === 'PageDown' || (event.key === ' ' && !event.shiftKey)\n        ? 1\n        : event.key === 'ArrowUp' || event.key === 'PageUp' || (event.key === ' ' && event.shiftKey)\n          ? -1\n          : 0;\n    if (step !== 0) {\n      event.preventDefault();\n      go(current + step);\n    } else if (event.key === 'Home' || event.key === 'End') {\n      event.preventDefault();\n      go(event.key === 'Home' ? 0 : count - 1);\n    }\n  };\n\n  const context = useMemo<SlidesContextValue>(\n    () => ({ snap, count, current, choose: settle, pressable: !snap && onIndexChange !== undefined }),\n    [snap, count, current, settle, onIndexChange]\n  );\n\n  return (\n    <SlidesContext.Provider value={context}>\n      <ScrollArea.Root\n        data-slot=\"sheet-slides\"\n        data-snap={snap ? '' : undefined}\n        className={slots.slides({ className })}\n        style={{ ...style, ['--slide-aspect' as string]: aspect } as CSSProperties}\n        {...props}\n      >\n        <ScrollArea.Viewport\n          ref={viewport}\n          tabIndex={0}\n          aria-roledescription=\"carousel\"\n          aria-label={label}\n          onKeyDown={keys}\n          className={slots.slidesViewport({\n            className: snap ? 'min-h-[min(60dvh,32rem)] snap-y snap-mandatory [container-type:size]' : undefined,\n          })}\n        >\n          <ScrollArea.Content className={slots.slidesContent({ className: snap ? 'py-1.5' : 'gap-2 pb-3 pt-1' })}>\n            {slides.map((slide, at) => (\n              <SlideIndexContext.Provider key={at} value={at}>\n                {slide}\n              </SlideIndexContext.Provider>\n            ))}\n          </ScrollArea.Content>\n        </ScrollArea.Viewport>\n        <ScrollArea.Scrollbar orientation=\"vertical\" className={slots.scrollbar()}>\n          <ScrollArea.Thumb className={slots.thumb()} />\n        </ScrollArea.Scrollbar>\n        {(counter ?? snap) && count > 1 ? (\n          <div data-slot=\"sheet-slides-counter\" aria-hidden=\"true\" className={slots.counter()}>\n            {current + 1} / {count}\n          </div>\n        ) : null}\n      </ScrollArea.Root>\n    </SlidesContext.Provider>\n  );\n}\nSheetSlides.displayName = 'Sheet.Slides';\n\nexport interface SheetSlideProps extends ComponentProps<'div'> {\n  /**\n   * What a screen reader calls it, after its place: \"3 of 8, Harbour at\n   * dawn\". An image's `alt` usually says it already.\n   */\n  label?: string;\n  /** Its shape in a column, overriding `Sheet.Slides`' `aspect`. */\n  aspect?: string;\n}\n\n/** One slide: a rounded frame that fills with an image, a video or anything drawn. */\nfunction SheetSlide({\n  label,\n  aspect,\n  className,\n  style,\n  children,\n  render,\n  ...props\n}: SheetSlideProps & RenderProp<'div'>) {\n  const slides = useContext(SlidesContext);\n  if (!slides) throw new Error('Sheet.Slide must be used inside <Sheet.Slides>.');\n  const at = useContext(SlideIndexContext);\n  const slots = sheetVariants();\n  const place = `${at + 1} of ${slides.count}`;\n  const selected = at === slides.current;\n  const name = label ? `${place}, ${label}` : place;\n  const frameClass = slots.slide({\n    className: [\n      slides.snap\n        ? 'h-[calc(100cqh-0.75rem)] snap-center'\n        : // In a column a slide sits on the sheet's own colour, so it takes the\n          // theme's border as a real edge, and a shadow to stand off the sheet.\n          'aspect-(--slide-aspect) rounded-[14px] shadow-xs ring-1 ring-border transition-shadow duration-150 after:hidden',\n      !slides.snap && !selected && slides.pressable && 'group-hover/slide:ring-foreground/25',\n      // Chosen in a column: a ring in the info colour, the theme's colour for\n      // something switched on, clear of the slide's edge.\n      !slides.snap && selected && 'shadow-none ring-2 ring-[var(--info)] ring-offset-2 ring-offset-popover',\n      className,\n    ],\n  });\n  const frameStyle = aspect ? { ...style, aspectRatio: aspect } : style;\n\n  const frame = (\n    <div data-slide-frame=\"\" data-current={selected ? '' : undefined} className={frameClass} style={frameStyle}>\n      {children}\n    </div>\n  );\n\n  // Called before the branch below, so a prop that changes later cannot change the hook count.\n  const rendered = useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'sheet-slide',\n        'data-index': at,\n        'data-selected': selected ? '' : undefined,\n        className: slots.slideRow(),\n        children: (\n          <>\n            <span\n              aria-hidden=\"true\"\n              className={slots.slideNumber({ className: selected ? 'text-[var(--info)]' : undefined })}\n            >\n              {at + 1}\n            </span>\n            {slides.pressable ? (\n              <button\n                type=\"button\"\n                aria-label={label ? name : `Slide ${place}`}\n                aria-current={selected ? 'true' : undefined}\n                className={slots.slideButton()}\n                onClick={() => slides.choose(at)}\n              >\n                {frame}\n              </button>\n            ) : (\n              <div role=\"group\" aria-roledescription=\"slide\" aria-label={name}>\n                {frame}\n              </div>\n            )}\n          </>\n        ),\n      },\n      props\n    ),\n  });\n  // In a deck the slide is the frame, so the observer that finds the current\n  // one has a box to watch.\n  if (slides.snap) {\n    return (\n      <div\n        data-slot=\"sheet-slide\"\n        data-slide-frame=\"\"\n        data-index={at}\n        data-current={selected ? '' : undefined}\n        role=\"group\"\n        aria-roledescription=\"slide\"\n        aria-label={name}\n        className={frameClass}\n        style={frameStyle}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n  return rendered;\n}\nSheetSlide.displayName = 'Sheet.Slide';\n\nexport interface SheetStageProps extends ComponentProps<'div'> {}\n\n/**\n * Wrap the app — or the part of the page a sheet belongs to — in this, and\n * the page steps back slightly while a sheet is open. Sheets opened anywhere\n * inside it take part.\n */\nfunction SheetStage({ className, children, ...props }: SheetStageProps) {\n  const slots = sheetVariants();\n  return (\n    <Drawer.Provider>\n      <div className={slots.stage({ className })} {...props}>\n        <Drawer.IndentBackground className={slots.stageBackground()} />\n        <Drawer.Indent className={slots.stagePage()}>{children}</Drawer.Indent>\n      </div>\n    </Drawer.Provider>\n  );\n}\nSheetStage.displayName = 'Sheet.Stage';\n\nexport const Sheet = Object.assign(SheetRoot, {\n  Trigger: SheetTrigger,\n  Next: SheetNext,\n  Content: SheetContent,\n  Header: SheetHeader,\n  Title: SheetTitle,\n  Description: SheetDescription,\n  Body: SheetBody,\n  Footer: SheetFooter,\n  Close: SheetClose,\n  Stage: SheetStage,\n  Slides: SheetSlides,\n  Slide: SheetSlide,\n});\n\nexport {\n  SheetTrigger,\n  SheetNext,\n  SheetContent,\n  SheetHeader,\n  SheetTitle,\n  SheetDescription,\n  SheetBody,\n  SheetFooter,\n  SheetClose,\n  SheetStage,\n  SheetSlides,\n  SheetSlide,\n};\n"
    }
  ]
}
