{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sheet", "title": "Sheet", "description": "Extends the Dialog component to display content that complements the main content of the screen.", "dependencies": [ "@phosphor-icons/react", "class-variance-authority", "clsx", "radix-ui", "tailwind-merge" ], "registryDependencies": [ "@sistine/theme" ], "files": [ { "path": "lib/utils.ts", "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", "type": "registry:lib", "target": "lib/utils.ts" }, { "path": "lib/material.ts", "content": "import type * as React from \"react\";\n\n/**\n * The Sistine material system — the single source of the material → markup mapping.\n *\n * FOUR materials (glass, frosted, crystal, opaque), each a `[data-material]` token set in\n * app/theme/materials.css, composed with orthogonal AXES (border, veil, size, gradient, glow, sheen)\n * over the one structural `glass` utility. Components call `glassMaterial()` instead of hardcoding\n * recipe class strings (check-theme bans the legacy `glass-bg`/`glass-surface`/… classes).\n *\n * Semantics:\n * - `material` UNDEFINED = adaptive: no attribute is rendered; the surface follows the page's\n * [data-glass] style (the default everywhere).\n * - An explicit material PINS the element under any page style, and — because the token set is\n * inherited custom properties — also acts as a scoped default for adaptive glass descendants\n * (a dialog set to frosted re-skins the controls inside it; pin a child back with its own material).\n * - `\"none\"` = not a glass surface: the helper returns nothing and the component supplies its plain\n * (shadcn) classes.\n */\nexport type Material = \"glass\" | \"frosted\" | \"crystal\" | \"opaque\" | \"none\";\n\nexport interface MaterialProps {\n /** undefined = adaptive (follows the page style); \"none\" = the component's plain fallback. */\n material?: Material;\n /** Material border. Three weights: `true`/\"hairline\" = the material's edge (1px; 0.5px under\n * frosted), \"rim\" = 2px, \"frame\" = 4px. */\n border?: boolean | \"hairline\" | \"rim\" | \"frame\";\n /** Element-composed legibility floor for read-through overlays (menus, tooltips, toasts). */\n veil?: boolean;\n /** Blur/elevation tier — INTERNAL: set by a component's ROLE (glass-sm/-lg), not a public prop\n * (\"size\" universally means a component's dimensions; the material tier must never shadow that). */\n size?: \"sm\" | \"lg\" | \"xl\";\n /** Readability blur FLOOR (>= --glass-diffuse, default 12px) for text-dense translucent surfaces;\n * inert under opaque. \"stained\" = the dyed mode: same floor, plus true stained-glass optics — the\n * backdrop renders as TONAL SHADES of the theme hue (grayscale luminance collapse — the tint supplies the color). */\n diffuse?: boolean | \"stained\";\n /** Brand-gradient accent layered over the material. */\n gradient?: boolean;\n /** Resting glow, or the intensified \"lg\" halo. */\n glow?: boolean | \"lg\";\n /** Opt-in hover shimmer (pseudo-element based — avoid on sticky/fixed surfaces). */\n sheen?: boolean;\n}\n\n/** The PUBLIC per-instance axis props a component spreads onto its type. Excludes `size` (the material\n * blur tier is a role-internal detail — a component's own `size` prop owns that name). */\nexport type MaterialAxisProps = Omit;\n\n/** The `rest` side of splitAxisProps. Distributes over union props (DayPicker's mode union, Radix\n * ToggleGroup's single/multiple) so a discriminated union survives the split instead of collapsing\n * under a bare Omit. */\nexport type SplitAxisRest

= P extends unknown ? Omit : never;\n\n/** Split the material axis props off a component's props in ONE place — components never enumerate\n * axis keys, so a NEW axis is zero-touch: extend MaterialProps + this destructure, done. `size` is\n * role-internal (not in MaterialAxisProps) and stays in `rest` for the component's own cva. */\nexport function splitAxisProps

(\n props: P,\n): [\n MaterialAxisProps,\n SplitAxisRest

,\n] {\n const { material, border, veil, diffuse, gradient, glow, sheen, ...rest } = props;\n return [\n {\n material,\n border,\n veil,\n diffuse,\n gradient,\n glow,\n sheen,\n },\n /* The conditional (distributive) rest type can't be proven for an unresolved P — safe by\n * construction: rest is exactly P minus the axis keys. */\n rest as SplitAxisRest

,\n ];\n}\n\nexport interface MaterialAttrs {\n /** Spread-ready: undefined for adaptive and \"none\". */\n \"data-material\"?: Exclude;\n /** \"\" when material === \"none\" (caller supplies its plain classes). */\n className: string;\n}\n\nexport function glassMaterial(props: MaterialProps = {}): MaterialAttrs {\n const { material, border, veil, size, diffuse, gradient, glow, sheen } = props;\n if (material === \"none\") {\n return {\n className: \"\",\n };\n }\n return {\n \"data-material\": material,\n className: [\n \"glass\",\n border && \"glass-border\",\n border === \"rim\" && \"glass-border-rim\",\n border === \"frame\" && \"glass-border-frame\",\n veil && \"glass-veil\",\n size === \"sm\" && \"glass-sm\",\n size === \"lg\" && \"glass-lg\",\n size === \"xl\" && \"glass-xl\",\n diffuse && \"glass-diffuse\",\n diffuse === \"stained\" && \"glass-stained\",\n gradient && \"glass-gradient\",\n glow === \"lg\" ? \"glass-glow-lg\" : glow ? \"glass-glow\" : false,\n sheen && \"glass-sheen\",\n ]\n .filter(Boolean)\n .join(\" \"),\n };\n}\n\n/**\n * The component-facing resolver: merge a component's ROLE (its default axes for a given semantic\n * variant) with explicit per-instance material/axis props, and return the surface markup — or null\n * when the element is NOT a glass surface (role === null, or material === \"none\").\n *\n * Explicit props override the role per-axis; unspecified axes fall back to the role.\n */\nexport function materialSurface(role: MaterialProps | null, props: MaterialProps): MaterialAttrs | null {\n if (props.material === \"none\" || role === null) {\n return null;\n }\n return glassMaterial({\n material: props.material,\n border: props.border ?? role.border,\n veil: props.veil ?? role.veil,\n size: props.size ?? role.size,\n diffuse: props.diffuse ?? role.diffuse,\n gradient: props.gradient ?? role.gradient,\n glow: props.glow ?? role.glow,\n sheen: props.sheen ?? role.sheen,\n });\n}\n\n/**\n * Typed per-element knobs → CSS custom properties ONLY (the cascade-native successor to the old\n * `glass={{…}}` inline-style prop). Everything routes through the token system, so nothing here can\n * fight a material, a page style, or the theme.\n *\n * The tint* keys recolor engine-composed tokens, which re-resolve at grouped declarations — pair\n * them with a `data-glass-tint` attribute on the same element so the engine re-composes there.\n */\nexport interface GlassVars {\n /** → --glass-tint-h (surface hue) */\n tintH?: number;\n /** → --glass-tint-c (surface chroma — the colorfulness master) */\n tintC?: number;\n /** → --glass-tint-a (tint film alpha) */\n tintA?: number;\n /** → --glass-opacity (0..1 solidify dial; element-composed in `glass`) */\n opacity?: number;\n /** → --srf-blur (number → px; glass material only) */\n blur?: number | string;\n /** → --glass-solid-a (veil floor alpha; element-composed in `glass-veil`) */\n solidA?: number;\n}\n\nexport function glassVars(v: GlassVars = {}): React.CSSProperties {\n const s: Record = {};\n if (v.tintH !== undefined) {\n s[\"--glass-tint-h\"] = String(v.tintH);\n }\n if (v.tintC !== undefined) {\n s[\"--glass-tint-c\"] = String(v.tintC);\n }\n if (v.tintA !== undefined) {\n s[\"--glass-tint-a\"] = String(v.tintA);\n }\n if (v.opacity !== undefined) {\n s[\"--glass-opacity\"] = String(v.opacity);\n }\n if (v.blur !== undefined) {\n s[\"--srf-blur\"] = typeof v.blur === \"number\" ? `${v.blur}px` : v.blur;\n }\n if (v.solidA !== undefined) {\n s[\"--glass-solid-a\"] = String(v.solidA);\n }\n return s as React.CSSProperties;\n}\n", "type": "registry:lib", "target": "lib/material.ts" }, { "path": "lib/hover-effects.ts", "content": "import { cva } from \"class-variance-authority\";\n\n/**\n * Shared hover effect variants for all Sistine components\n *\n * Effects:\n * - none: No hover effect\n * - glow: Themed glow shadow (--glass-glow, follows the tint hue) that intensifies on hover\n * - shimmer: Moving shimmer effect across the component\n * - ripple: Ripple effect that scales outward on hover\n * - lift: Subtle lift effect with shadow increase\n * - scale: Scale up slightly on hover\n */\nexport const hoverEffects = cva(\"transition duration-300\", {\n variants: {\n hover: {\n none: \"\",\n glow: \"glass-glow hover:glass-glow-lg\",\n shimmer:\n \"relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:bg-gradient-to-r before:from-transparent before:via-white/20 before:to-transparent hover:before:translate-x-full before:transition-transform before:duration-1000\",\n ripple:\n \"relative overflow-hidden after:absolute after:inset-0 after:scale-0 after:rounded-full after:bg-white/30 after:transition-transform after:duration-500 hover:after:scale-150\",\n lift: \"hover:-translate-y-1 hover:shadow-lg\",\n scale: \"hover:scale-105\",\n },\n },\n defaultVariants: {\n hover: \"none\",\n },\n});\n\nexport type HoverEffect = \"none\" | \"glow\" | \"shimmer\" | \"ripple\" | \"lift\" | \"scale\";\n", "type": "registry:lib", "target": "lib/hover-effects.ts" }, { "path": "components/ui/sheet.tsx", "content": "\"use client\";\n\nimport { X } from \"@phosphor-icons/react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { type MaterialAxisProps, type MaterialProps, materialSurface, splitAxisProps } from \"@/lib/material\";\nimport { cn } from \"@/lib/utils\";\n\n/* Role: bordered adaptive glass at the elevated blur tier. */\nconst ROLE: MaterialProps = {\n border: true,\n size: \"lg\",\n};\n\nconst Sheet = DialogPrimitive.Root;\n\nconst SheetTrigger = DialogPrimitive.Trigger;\n\nconst SheetClose = DialogPrimitive.Close;\n\nconst SheetPortal = DialogPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSheetOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n \"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out\",\n {\n variants: {\n side: {\n top: \"inset-x-0 top-0 rounded-b-xl border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom: \"inset-x-0 bottom-0 rounded-t-xl border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n left: \"inset-y-0 left-0 h-full w-3/4 rounded-r-xl border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n right:\n \"inset-y-0 right-0 h-full w-3/4 rounded-l-xl border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n },\n },\n defaultVariants: {\n side: \"right\",\n },\n },\n);\n\ninterface SheetContentProps\n extends React.ComponentPropsWithoutRef,\n VariantProps,\n MaterialAxisProps {\n variant?: \"default\" | \"glass\";\n showCloseButton?: boolean;\n}\n\nconst SheetContent = React.forwardRef, SheetContentProps>(\n ({ side = \"right\", variant = \"glass\", className, children, showCloseButton = true, ...props }, ref) => {\n const [axes, rest] = splitAxisProps(props);\n /* Variant classes carry BEHAVIOR only; the surface comes from materialSurface. */\n const getVariantClass = () => (variant === \"default\" ? \"bg-background border\" : \"text-foreground\");\n\n const m = materialSurface(variant === \"default\" ? null : ROLE, axes);\n\n return (\n \n \n \n {children}\n {showCloseButton && (\n \n \n Close\n \n )}\n \n \n );\n },\n);\nSheetContent.displayName = DialogPrimitive.Content.displayName;\n\nconst SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (\n

\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (\n
\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef, React.ComponentPropsWithoutRef>(\n ({ className, ...props }, ref) => (\n \n ),\n);\nSheetTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nSheetDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger };\n", "type": "registry:component", "target": "components/ui/sheet.tsx" } ], "type": "registry:component" }