{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sheet",
  "type": "registry:ui",
  "title": "Sheet",
  "description": "Side sheet overlay for Tile UI.",
  "dependencies": [
    "@radix-ui/react-slot",
    "react-dom"
  ],
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/portal",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/sheet/sheet.tsx",
      "type": "registry:ui",
      "target": "components/ui/sheet/sheet.tsx",
      "content": "import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Slot } from '@radix-ui/react-slot';\nimport { getSheetState, getSheetTranslateStyle, sheetStyleKeys } from '../lib/core';\nimport type { SheetBaseProps, SheetSide } from '../lib/core';\nimport { usePortalContainer, type PortalContainer } from '../portal';\nimport styles from './sheet.module.scss';\n\ninterface SheetContextValue {\n\topen: boolean;\n\tsetOpen: (open: boolean) => void;\n\tclose: () => void;\n\ttitleId: string;\n\tdescriptionId: string;\n}\n\nconst SheetContext = createContext<SheetContextValue | null>(null);\n\nfunction useSheetContext(): SheetContextValue {\n\tconst context = useContext(SheetContext);\n\tif (!context) {\n\t\tthrow new Error('Sheet sub-components must be used within <Sheet>.');\n\t}\n\treturn context;\n}\n\nexport interface SheetProps extends SheetBaseProps {\n\topen?: boolean;\n\tdefaultOpen?: boolean;\n\tonOpenChange?: (open: boolean) => void;\n\tchildren?: React.ReactNode;\n}\n\nfunction Sheet({ open, defaultOpen = false, onOpenChange, children }: SheetProps) {\n\tconst [internalOpen, setInternalOpen] = useState(defaultOpen);\n\tconst onOpenChangeRef = useRef(onOpenChange);\n\n\tuseEffect(() => {\n\t\tonOpenChangeRef.current = onOpenChange;\n\t});\n\n\tconst setOpen = useCallback(\n\t\t(next: boolean) => {\n\t\t\tif (open === undefined) {\n\t\t\t\tsetInternalOpen(next);\n\t\t\t}\n\n\t\t\tonOpenChangeRef.current?.(next);\n\t\t},\n\t\t[open],\n\t);\n\n\tconst close = useCallback(() => {\n\t\tsetOpen(false);\n\t}, [setOpen]);\n\n\tconst isOpen = open !== undefined ? open : internalOpen;\n\tconst titleId = useId();\n\tconst descriptionId = useId();\n\n\treturn <SheetContext.Provider value={{ open: isOpen, setOpen, close, titleId, descriptionId }}>{children}</SheetContext.Provider>;\n}\n\nSheet.displayName = 'Sheet';\n\nexport interface SheetTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n\tasChild?: boolean;\n}\n\nconst SheetTrigger = React.forwardRef<HTMLButtonElement, SheetTriggerProps>(({ asChild = false, className = '', children, onClick, ...props }, ref) => {\n\tconst context = useSheetContext();\n\tconst Comp = asChild ? Slot : 'button';\n\n\treturn (\n\t\t<Comp\n\t\t\tref={ref}\n\t\t\ttype={asChild ? undefined : 'button'}\n\t\t\tclassName={className}\n\t\t\tonClick={(event: React.MouseEvent<HTMLButtonElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.setOpen(true);\n\t\t\t}}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nSheetTrigger.displayName = 'SheetTrigger';\n\nexport interface SheetCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n\tasChild?: boolean;\n}\n\nconst SheetClose = React.forwardRef<HTMLButtonElement, SheetCloseProps>(({ asChild = false, className = '', children, onClick, ...props }, ref) => {\n\tconst context = useSheetContext();\n\tconst Comp = asChild ? Slot : 'button';\n\n\treturn (\n\t\t<Comp\n\t\t\tref={ref}\n\t\t\ttype={asChild ? undefined : 'button'}\n\t\t\tclassName={className}\n\t\t\tonClick={(event: React.MouseEvent<HTMLButtonElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.close();\n\t\t\t}}\n\t\t\t{...props}>\n\t\t\t{children}\n\t\t</Comp>\n\t);\n});\nSheetClose.displayName = 'SheetClose';\n\nexport interface SheetOverlayProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst SheetOverlay = React.forwardRef<HTMLDivElement, SheetOverlayProps>(({ className = '', onClick, ...props }, ref) => {\n\tconst context = useSheetContext();\n\n\tif (!context.open) {\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tref={ref}\n\t\t\tdata-state={getSheetState(context.open)}\n\t\t\tclassName={`${styles[sheetStyleKeys.overlay]} ${className}`}\n\t\t\tonClick={(event: React.MouseEvent<HTMLDivElement>) => {\n\t\t\t\tonClick?.(event);\n\t\t\t\tcontext.close();\n\t\t\t}}\n\t\t\t{...props}\n\t\t/>\n\t);\n});\nSheetOverlay.displayName = 'SheetOverlay';\n\nexport interface SheetContentProps extends React.HTMLAttributes<HTMLDivElement> {\n\tside?: SheetSide;\n\tshowCloseButton?: boolean;\n\tcontainer?: PortalContainer;\n}\n\nconst SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(\n\t({ className = '', side = 'right', showCloseButton = true, container, style, children, ...props }, ref) => {\n\t\tconst { open, close, titleId, descriptionId } = useSheetContext();\n\t\tconst portalContainer = usePortalContainer(container);\n\t\tconst contentRef = useRef<HTMLDivElement | null>(null);\n\t\tconst [isVisible, setIsVisible] = useState(false);\n\n\t\tfunction setContentRef(element: HTMLDivElement | null) {\n\t\t\tcontentRef.current = element;\n\n\t\t\tif (typeof ref === 'function') {\n\t\t\t\tref(element);\n\t\t\t} else if (ref) {\n\t\t\t\tref.current = element;\n\t\t\t}\n\t\t}\n\n\t\tuseLayoutEffect(() => {\n\t\t\tif (!open) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst previouslyFocused = document.activeElement as HTMLElement | null;\n\t\t\tconst previousOverflow = document.body.style.overflow;\n\t\t\tdocument.body.style.overflow = 'hidden';\n\n\t\t\tconst handleKeyDown = (event: KeyboardEvent) => {\n\t\t\t\tif (event.key === 'Escape') {\n\t\t\t\t\tclose();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.key === 'Tab') {\n\t\t\t\t\tconst container = contentRef.current;\n\t\t\t\t\tif (!container) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst focusables = Array.from(\n\t\t\t\t\t\tcontainer.querySelectorAll<HTMLElement>(\n\t\t\t\t\t\t\t'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n\t\t\t\t\t\t),\n\t\t\t\t\t).filter((el) => el.getClientRects().length > 0);\n\t\t\t\t\tif (focusables.length === 0) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst first = focusables[0];\n\t\t\t\t\tconst last = focusables[focusables.length - 1];\n\t\t\t\t\tconst active = document.activeElement;\n\n\t\t\t\t\tif (event.shiftKey) {\n\t\t\t\t\t\tif (active === first || active === container || !container.contains(active)) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tlast.focus();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (active === last || !container.contains(active)) {\n\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\tfirst.focus();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tdocument.addEventListener('keydown', handleKeyDown);\n\n\t\t\tconst contentEl = contentRef.current;\n\t\t\tif (contentEl) {\n\t\t\t\tcontentEl.focus();\n\t\t\t}\n\n\t\t\treturn () => {\n\t\t\t\tdocument.body.style.overflow = previousOverflow;\n\t\t\t\tdocument.removeEventListener('keydown', handleKeyDown);\n\t\t\t\tpreviouslyFocused?.focus();\n\t\t\t};\n\t\t}, [close, open]);\n\n\t\tuseEffect(() => {\n\t\t\tif (!open) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst frame = requestAnimationFrame(() => {\n\t\t\t\tsetIsVisible(true);\n\t\t\t});\n\n\t\t\treturn () => cancelAnimationFrame(frame);\n\t\t}, [open]);\n\n\t\tif (!open || !portalContainer) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn createPortal(\n\t\t\t<>\n\t\t\t\t<SheetOverlay />\n\t\t\t\t<div\n\t\t\t\t\tref={setContentRef}\n\t\t\t\t\trole=\"dialog\"\n\t\t\t\t\taria-modal=\"true\"\n\t\t\t\t\taria-labelledby={titleId}\n\t\t\t\t\taria-describedby={descriptionId}\n\t\t\t\t\ttabIndex={-1}\n\t\t\t\t\tdata-state={getSheetState(open)}\n\t\t\t\t\tdata-side={side}\n\t\t\t\t\tstyle={{ transform: isVisible ? '' : getSheetTranslateStyle(side), opacity: isVisible ? '' : 0, ...style }}\n\t\t\t\t\tclassName={`${styles[sheetStyleKeys.content]} ${className}`}\n\t\t\t\t\t{...props}>\n\t\t\t\t\t{children}\n\t\t\t\t\t{showCloseButton && (\n\t\t\t\t\t\t<button type=\"button\" aria-label=\"关闭\" className={styles[sheetStyleKeys.close]} onClick={() => close()}>\n\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\tclassName={styles[sheetStyleKeys.xIcon]}\n\t\t\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\t\t\twidth=\"16\"\n\t\t\t\t\t\t\t\theight=\"16\"\n\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t\t\t\t<path d=\"M18 6 6 18\" />\n\t\t\t\t\t\t\t\t<path d=\"m6 6 12 12\" />\n\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t</>,\n\t\t\tportalContainer,\n\t\t);\n\t},\n);\nSheetContent.displayName = 'SheetContent';\n\nexport interface SheetHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst SheetHeader = React.forwardRef<HTMLDivElement, SheetHeaderProps>(({ className = '', children, ...props }, ref) => {\n\treturn (\n\t\t<div ref={ref} className={`${styles[sheetStyleKeys.header]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n});\nSheetHeader.displayName = 'SheetHeader';\n\nexport interface SheetFooterProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nconst SheetFooter = React.forwardRef<HTMLDivElement, SheetFooterProps>(({ className = '', children, ...props }, ref) => {\n\treturn (\n\t\t<div ref={ref} className={`${styles[sheetStyleKeys.footer]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</div>\n\t);\n});\nSheetFooter.displayName = 'SheetFooter';\n\nexport interface SheetTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}\n\nconst SheetTitle = React.forwardRef<HTMLHeadingElement, SheetTitleProps>(({ className = '', children, ...props }, ref) => {\n\tconst context = useSheetContext();\n\n\treturn (\n\t\t<h2 ref={ref} id={context.titleId} className={`${styles[sheetStyleKeys.title]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</h2>\n\t);\n});\nSheetTitle.displayName = 'SheetTitle';\n\nexport interface SheetDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}\n\nconst SheetDescription = React.forwardRef<HTMLParagraphElement, SheetDescriptionProps>(({ className = '', children, ...props }, ref) => {\n\tconst context = useSheetContext();\n\n\treturn (\n\t\t<p ref={ref} id={context.descriptionId} className={`${styles[sheetStyleKeys.description]} ${className}`} {...props}>\n\t\t\t{children}\n\t\t</p>\n\t);\n});\nSheetDescription.displayName = 'SheetDescription';\n\nexport { Sheet, SheetTrigger, SheetClose, SheetOverlay, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };\nexport default Sheet;\n"
    },
    {
      "path": "packages/styles/scss/components/sheet.module.scss",
      "type": "registry:file",
      "target": "components/ui/sheet/sheet.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n// 半透明遮罩\n.overlay {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: $z-modal;\n\tbackground-color: var(--tile-sheet-overlay, $overlay);\n\n\t&[data-state='open'] {\n\t\tanimation: sheet-overlay-in $transition-normal;\n\t}\n\n\t&[data-state='closed'] {\n\t\topacity: 0;\n\t}\n}\n\n@keyframes sheet-overlay-in {\n\tfrom {\n\t\topacity: 0;\n\t}\n\n\tto {\n\t\topacity: 1;\n\t}\n}\n\n// 侧滑面板\n.content {\n\tposition: fixed;\n\tz-index: $z-modal;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: $spacing-4;\n\tbackground-color: var(--tile-sheet-background, $card);\n\tcolor: $card-foreground;\n\tbox-shadow: var(--tile-sheet-shadow, $shadow-lg);\n\toutline: none;\n\ttransition:\n\t\ttransform $transition-slow,\n\t\topacity $transition-fast;\n\n\t&[data-side='right'] {\n\t\tinset: 0 0 0 auto;\n\t\twidth: 75%;\n\t\theight: 100%;\n\t\tmax-width: 24rem;\n\t\tborder-left: 1px solid var(--tile-sheet-border, $border);\n\t}\n\n\t&[data-side='left'] {\n\t\tinset: 0 auto 0 0;\n\t\twidth: 75%;\n\t\theight: 100%;\n\t\tmax-width: 24rem;\n\t\tborder-right: 1px solid var(--tile-sheet-border, $border);\n\t}\n\n\t&[data-side='top'] {\n\t\tinset: 0 0 auto 0;\n\t\twidth: 100%;\n\t\tborder-bottom: 1px solid var(--tile-sheet-border, $border);\n\t}\n\n\t&[data-side='bottom'] {\n\t\tinset: auto 0 0 0;\n\t\twidth: 100%;\n\t\tborder-top: 1px solid var(--tile-sheet-border, $border);\n\t}\n}\n\n// 右上角关闭按钮\n.close {\n\tposition: absolute;\n\ttop: $spacing-4;\n\tright: $spacing-4;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: $spacing-1;\n\tborder: none;\n\tborder-radius: $radius;\n\tbackground-color: transparent;\n\tcolor: $muted-foreground;\n\topacity: 0.7;\n\tcursor: pointer;\n\ttransition: opacity $transition-fast;\n\toutline: none;\n\n\t&:hover {\n\t\topacity: 1;\n\t}\n\n\t&:focus-visible {\n\t\tbox-shadow: var(--tile-sheet-close-focus-shadow, 0 0 0 2px $background, 0 0 0 4px $ring);\n\t}\n\n\t.xIcon {\n\t\twidth: 1rem;\n\t\theight: 1rem;\n\t\tpointer-events: none;\n\t}\n}\n\n.header {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: $spacing-1_5;\n\tpadding: var(--tile-sheet-header-padding, $spacing-4);\n}\n\n.footer {\n\tmargin-top: auto;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: $spacing-2;\n\tpadding: var(--tile-sheet-footer-padding, $spacing-4);\n}\n\n.title {\n\tmargin: 0;\n\tfont-size: var(--tile-sheet-title-size, $text-base);\n\tfont-weight: $font-semibold;\n\tcolor: $foreground;\n}\n\n.description {\n\tmargin: 0;\n\tfont-size: $text-sm;\n\tline-height: $leading-normal;\n\tcolor: var(--tile-sheet-description-color, $muted-foreground);\n}\n"
    },
    {
      "path": "packages/react/src/components/sheet/index.ts",
      "type": "registry:ui",
      "target": "components/ui/sheet/index.ts",
      "content": "export {\n\tSheet,\n\tSheetTrigger,\n\tSheetClose,\n\tSheetOverlay,\n\tSheetContent,\n\tSheetHeader,\n\tSheetFooter,\n\tSheetTitle,\n\tSheetDescription,\n\ttype SheetProps,\n\ttype SheetTriggerProps,\n\ttype SheetCloseProps,\n\ttype SheetOverlayProps,\n\ttype SheetContentProps,\n\ttype SheetHeaderProps,\n\ttype SheetFooterProps,\n\ttype SheetTitleProps,\n\ttype SheetDescriptionProps,\n} from './sheet';\n"
    }
  ]
}
