{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"sheet","title":"Sheet","description":"Dialog-backed edge flyout for forms and save/cancel workflows.","dependencies":["@base-ui/react@1.7.0","lucide-react@1.18.0"],"registryDependencies":["@siteplane/base","@siteplane/button","@siteplane/scroll-area"],"files":[{"path":"packages/ui/src/components/sheet/sheet.tsx","content":"\"use client\";\n\nimport { Dialog as SheetPrimitive } from \"@base-ui/react/dialog\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { XIcon } from \"lucide-react\";\nimport type React from \"react\";\nimport { SiteplaneUIPortalScope } from \"../../system/provider/siteplane-ui-provider\";\nimport { surfaceClassNames } from \"../../system/styles/surface\";\nimport { cn, type WithStringClassName } from \"../../system/utils/cn\";\nimport { slotClass } from \"../../system/utils/slot-class\";\nimport { KitButton } from \"../button/button-internal\";\nimport { ScrollArea } from \"../scroll-area\";\nimport type { KitSheetPopupProps } from \"./sheet-internal\";\n\nimport \"./sheet.css\";\n\nexport const Sheet: typeof SheetPrimitive.Root = SheetPrimitive.Root;\n\n// Base UI's own Portal type, with className narrowed to a string.\nexport const SheetPortal: React.ForwardRefExoticComponent<\n  WithStringClassName<Omit<SheetPrimitive.Portal.Props, \"ref\">> &\n    React.RefAttributes<HTMLDivElement>\n> = SheetPrimitive.Portal;\n\nexport function SheetTrigger(\n  props: WithStringClassName<SheetPrimitive.Trigger.Props>,\n): React.ReactElement {\n  return (\n    <SheetPrimitive.Trigger\n      data-slot=\"sheet-trigger\"\n      {...props}\n      className={cn(slotClass(\"sheet-trigger\", props), props.className)}\n    />\n  );\n}\n\nexport function SheetClose(\n  props: WithStringClassName<SheetPrimitive.Close.Props>,\n): React.ReactElement {\n  return (\n    <SheetPrimitive.Close\n      data-slot=\"sheet-close\"\n      {...props}\n      className={cn(slotClass(\"sheet-close\", props), props.className)}\n    />\n  );\n}\n\nexport function SheetBackdrop({\n  className,\n  ...props\n}: WithStringClassName<SheetPrimitive.Backdrop.Props>): React.ReactElement {\n  return (\n    <SheetPrimitive.Backdrop\n      className={cn(\n        slotClass(\"sheet-backdrop\", props),\n        \"sp-sheet-backdrop-motion fixed inset-0 z-50\",\n        className,\n      )}\n      data-slot=\"sheet-backdrop\"\n      {...props}\n    />\n  );\n}\n\nexport function SheetViewport({\n  className,\n  side,\n  variant = \"default\",\n  ...props\n}: WithStringClassName<SheetPrimitive.Viewport.Props> & {\n  side?: \"right\" | \"left\" | \"top\" | \"bottom\";\n  variant?: \"default\" | \"inset\" | \"fullscreen\";\n}): React.ReactElement {\n  // A fullscreen viewport takes its display and padding from sheet.css.\n  const fullscreen = variant === \"fullscreen\";\n  return (\n    <SheetPrimitive.Viewport\n      className={cn(\n        slotClass(\"sheet-viewport\", props),\n        \"fixed inset-0 z-50\",\n        !fullscreen && \"grid\",\n        side === \"bottom\" && [\"grid-rows-[1fr_auto]\", !fullscreen && \"pt-12\"],\n        side === \"top\" && [\"grid-rows-[auto_1fr]\", !fullscreen && \"pb-12\"],\n        side === \"left\" && \"flex justify-start\",\n        side === \"right\" && \"flex justify-end\",\n        variant === \"inset\" && \"sm:p-4\",\n        className,\n      )}\n      data-slot=\"sheet-viewport\"\n      data-variant={variant}\n      {...props}\n    />\n  );\n}\n\ntype SheetPopupProps = WithStringClassName<SheetPrimitive.Popup.Props> & {\n  showCloseButton?: boolean;\n  side?: \"right\" | \"left\" | \"top\" | \"bottom\";\n  variant?: \"default\" | \"inset\" | \"fullscreen\";\n  closeProps?: WithStringClassName<SheetPrimitive.Close.Props>;\n  portalProps?: WithStringClassName<SheetPrimitive.Portal.Props>;\n};\n\nexport function SheetPopup(popupProps: SheetPopupProps): React.ReactElement {\n  // Sidebar passes the internal sidebar profile through KitSheetPopup\n  // (sheet-internal.ts), the same function.\n  const {\n    className,\n    children,\n    showCloseButton = true,\n    side = \"right\",\n    variant = \"default\",\n    profile = \"default\",\n    closeProps,\n    portalProps,\n    ...props\n  } = popupProps as KitSheetPopupProps;\n  return (\n    <SheetPortal {...portalProps}>\n      <SiteplaneUIPortalScope>\n        <SheetBackdrop />\n        <SheetViewport side={side} variant={variant}>\n          <SheetPrimitive.Popup\n            className={cn(\n              slotClass(\"sheet-popup\", props),\n              surfaceClassNames.edgeOverlay,\n              \"sp-sheet-panel-motion flex max-h-full min-h-0 w-full min-w-0 flex-col outline-none\",\n              side === \"bottom\" && \"row-start-2\",\n              side === \"right\" && \"col-start-2\",\n              // A fullscreen sheet takes the whole viewport (sheet.css).\n              (side === \"left\" || side === \"right\") &&\n                variant !== \"fullscreen\" &&\n                \"w-[calc(100%-(--spacing(12)))] max-w-md\",\n              variant === \"inset\" &&\n                \"sm:rounded-2xl sm:before:rounded-[inherit]\",\n              profile === \"sidebar\" && [\n                variant !== \"fullscreen\" && \"w-(--sidebar-width)\",\n                \"bg-[var(--sp-rw-bg-base)] p-0 text-[var(--sp-rw-fg-primary)]\",\n              ],\n              className,\n            )}\n            data-profile={profile}\n            data-variant={variant}\n            data-side={side}\n            data-slot=\"sheet-popup\"\n            {...props}\n          >\n            {children}\n            {showCloseButton && (\n              // The corner close of every overlay: the 24 px overlay-dismiss\n              // Button 8 px inside the top-end corner. The header keeps its\n              // text clear of it (design-rework.css).\n              <SheetClose\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              </SheetClose>\n            )}\n          </SheetPrimitive.Popup>\n        </SheetViewport>\n      </SiteplaneUIPortalScope>\n    </SheetPortal>\n  );\n}\n\nexport function SheetHeader({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\">): React.ReactElement {\n  const defaultProps = {\n    className: cn(\n      slotClass(\"sheet-header\", props, render),\n      \"flex flex-col\",\n      className,\n    ),\n    \"data-slot\": \"sheet-header\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function SheetFooter({\n  className,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\">): 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(\"sheet-footer\", props, render),\n      \"flex flex-wrap\",\n      className,\n    ),\n    \"data-slot\": \"sheet-footer\",\n  };\n\n  return useRender({\n    defaultTagName: \"div\",\n    props: mergeProps<\"div\">(defaultProps, props),\n    render,\n  });\n}\n\nexport function SheetTitle({\n  className,\n  ...props\n}: WithStringClassName<SheetPrimitive.Title.Props>): React.ReactElement {\n  return (\n    <SheetPrimitive.Title\n      className={cn(\n        slotClass(\"sheet-title\", props),\n        \"font-heading wrap-anywhere\",\n        className,\n      )}\n      data-slot=\"sheet-title\"\n      {...props}\n    />\n  );\n}\n\nexport function SheetDescription({\n  className,\n  ...props\n}: WithStringClassName<SheetPrimitive.Description.Props>): React.ReactElement {\n  return (\n    <SheetPrimitive.Description\n      className={cn(\n        slotClass(\"sheet-description\", props),\n        \"wrap-anywhere\",\n        className,\n      )}\n      data-slot=\"sheet-description\"\n      {...props}\n    />\n  );\n}\n\nexport function SheetPanel({\n  className,\n  scrollFade = true,\n  render,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  scrollFade?: boolean;\n}): React.ReactElement {\n  const defaultProps = {\n    className: cn(slotClass(\"sheet-panel\", props, render), className),\n    \"data-slot\": \"sheet-panel\",\n  };\n\n  return (\n    <ScrollArea scrollFade={scrollFade}>\n      {useRender({\n        defaultTagName: \"div\",\n        props: mergeProps<\"div\">(defaultProps, props),\n        render,\n      })}\n    </ScrollArea>\n  );\n}\n\n// SheetOverlay and SheetContent are the shadcn names of SheetBackdrop and\n// SheetPopup.\nexport {\n  SheetPrimitive,\n  SheetBackdrop as SheetOverlay,\n  SheetPopup as SheetContent,\n};\n","type":"registry:ui","target":"~/src/siteplane/components/sheet/sheet.tsx"},{"path":"packages/ui/src/components/sheet/sheet-internal.ts","content":"import type * as React from \"react\";\n\nimport { SheetPopup } from \"./sheet\";\n\n/*\n * Kit-internal SheetPopup. Not public API: the sheet index does not export\n * this module, and its names and profiles may change in any release. Sidebar\n * renders its mobile sheet with the sidebar profile.\n */\nexport type SheetPopupProfile = \"default\" | \"sidebar\";\n\nexport type KitSheetPopupProps = React.ComponentProps<typeof SheetPopup> & {\n  profile?: SheetPopupProfile;\n};\n\n/** SheetPopup with the kit's composition profile; the same component. */\nexport const KitSheetPopup = SheetPopup as (\n  props: KitSheetPopupProps,\n) => React.ReactElement;\n","type":"registry:ui","target":"~/src/siteplane/components/sheet/sheet-internal.ts"},{"path":"packages/ui/src/components/sheet/sheet.css","content":"@layer theme, base, components, siteplane, utilities;\n\n@layer siteplane {\n  .sp-sheet-backdrop-motion {\n    transition-property: opacity;\n    transition-duration: var(--motion-duration-panel);\n    transition-timing-function: var(--motion-ease-panel);\n    will-change: opacity;\n  }\n\n  .sp-sheet-backdrop-motion[data-starting-style],\n  .sp-sheet-backdrop-motion[data-ending-style] {\n    opacity: 0;\n  }\n\n  .sp-sheet-panel-motion {\n    transition-property: transform;\n    transition-duration: var(--motion-duration-panel);\n    transition-timing-function: var(--motion-ease-panel);\n    will-change: transform;\n  }\n\n  .sp-sheet-panel-motion[data-side=\"right\"][data-starting-style],\n  .sp-sheet-panel-motion[data-side=\"right\"][data-ending-style] {\n    transform: translateX(calc(100% + 1rem));\n  }\n\n  .sp-sheet-panel-motion[data-side=\"left\"][data-starting-style],\n  .sp-sheet-panel-motion[data-side=\"left\"][data-ending-style] {\n    transform: translateX(calc(-100% - 1rem));\n  }\n\n  /* The viewport places a side sheet at the inline start or end\n   * (justify-start, justify-end), so in a right-to-left page a left sheet\n   * sits on the right, as the Sidebar's mobile sheet does. It slides in from\n   * the edge it sits at. The dir attribute is named beside :dir(rtl), which a\n   * build for older browsers turns into a list of right-to-left languages. */\n  .sp-sheet-panel-motion[data-side=\"right\"][data-starting-style]:is(\n      :dir(rtl),\n      [dir=\"rtl\"],\n      [dir=\"rtl\"] *\n    ),\n  .sp-sheet-panel-motion[data-side=\"right\"][data-ending-style]:is(\n      :dir(rtl),\n      [dir=\"rtl\"],\n      [dir=\"rtl\"] *\n    ) {\n    transform: translateX(calc(-100% - 1rem));\n  }\n\n  .sp-sheet-panel-motion[data-side=\"left\"][data-starting-style]:is(\n      :dir(rtl),\n      [dir=\"rtl\"],\n      [dir=\"rtl\"] *\n    ),\n  .sp-sheet-panel-motion[data-side=\"left\"][data-ending-style]:is(\n      :dir(rtl),\n      [dir=\"rtl\"],\n      [dir=\"rtl\"] *\n    ) {\n    transform: translateX(calc(100% + 1rem));\n  }\n\n  .sp-sheet-panel-motion[data-side=\"bottom\"][data-starting-style],\n  .sp-sheet-panel-motion[data-side=\"bottom\"][data-ending-style] {\n    transform: translateY(calc(100% + 1rem));\n  }\n\n  .sp-sheet-panel-motion[data-side=\"top\"][data-starting-style],\n  .sp-sheet-panel-motion[data-side=\"top\"][data-ending-style] {\n    transform: translateY(calc(-100% - 1rem));\n  }\n\n  /* Fullscreen navigation is a Sheet surface, including its nested subpages. */\n  :root .sp-sheet-viewport[data-variant=\"fullscreen\"] {\n    display: flex;\n    padding: 0;\n  }\n  :root .sp-sheet-panel-motion[data-variant=\"fullscreen\"] {\n    width: 100%;\n    max-width: none;\n    height: 100%;\n    border: 0;\n    border-radius: 0;\n    background: var(--sp-rw-bg-base);\n    box-shadow: none;\n    transition-duration: var(--motion-duration-slow);\n    transition-timing-function: var(--motion-ease-standard);\n  }\n  :root .sp-sheet-panel-motion[data-variant=\"fullscreen\"][data-side=\"top\"] {\n    transition-property: opacity, transform;\n  }\n  :root\n    .sp-sheet-panel-motion[data-variant=\"fullscreen\"][data-side=\"top\"]:is(\n      [data-starting-style],\n      [data-ending-style]\n    ) {\n    opacity: 0;\n    transform: translateY(calc(-1 * var(--sp-rw-space-4)));\n  }\n  html[data-motion-reduce=\"true\"] .sp-sheet-panel-motion[data-starting-style],\n  html[data-motion-reduce=\"true\"] .sp-sheet-panel-motion[data-ending-style],\n  :where([data-sp-motion=\"reduced\"])\n    .sp-sheet-panel-motion[data-starting-style],\n  :where([data-sp-motion=\"reduced\"]) .sp-sheet-panel-motion[data-ending-style] {\n    transform: none !important;\n  }\n\n  html[data-motion-reduce=\"true\"] .sp-sheet-backdrop-motion,\n  html[data-motion-reduce=\"true\"] .sp-sheet-panel-motion,\n  :where([data-sp-motion=\"reduced\"]) .sp-sheet-backdrop-motion,\n  :where([data-sp-motion=\"reduced\"]) .sp-sheet-panel-motion {\n    transition-duration: 1ms !important;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    .sp-sheet-panel-motion[data-starting-style],\n    .sp-sheet-panel-motion[data-ending-style] {\n      transform: none !important;\n    }\n\n    .sp-sheet-backdrop-motion,\n    .sp-sheet-panel-motion {\n      transition-duration: 1ms !important;\n    }\n  }\n}\n","type":"registry:ui","target":"~/src/siteplane/components/sheet/sheet.css"},{"path":"packages/ui/src/components/sheet/index.ts","content":"export * from \"./sheet\";\n","type":"registry:ui","target":"~/src/siteplane/components/sheet/index.ts"}],"type":"registry:ui","docs":"https://ui.siteplane.io/docs-markdown/components/sheet.md","categories":["Overlays & disclosure"],"meta":{"avoidWhen":["Avoid for a decision that needs an answer before continuing; use Alert Dialog.","Avoid when swipe dismissal is wanted; use Drawer."],"description":"Dialog-backed edge flyout for forms and save/cancel workflows.","knownLimitations":[],"providerNotes":[],"releaseStatus":"stable","searchTerms":["sheet"],"useWhen":["Use for a form or a short task in a panel at the edge of the screen, with save and cancel actions."]}}