Spaces:
Running
Running
| <script lang="ts" module> | |
| import type { Snippet } from 'svelte'; | |
| export type SheetSide = 'top' | 'right' | 'bottom' | 'left'; | |
| export type SheetProps = { | |
| open?: boolean; | |
| titleText?: string; | |
| description?: string; | |
| side?: SheetSide; | |
| lockScroll?: boolean; | |
| contentClass?: string; | |
| headerClass?: string; | |
| titleClass?: string; | |
| header?: Snippet; | |
| title?: Snippet; | |
| children?: Snippet; | |
| }; | |
| </script> | |
| <script lang="ts"> | |
| import * as UISheet from '$lib/components/ui/sheet'; | |
| import { cn } from '$lib/utils.js'; | |
| let { | |
| open = $bindable(false), | |
| titleText = '', | |
| description, | |
| side = 'right', | |
| lockScroll = true, | |
| contentClass, | |
| headerClass, | |
| titleClass, | |
| header, | |
| title, | |
| children | |
| }: SheetProps = $props(); | |
| let contentElement = $state<HTMLElement | null>(null); | |
| let restoreFocusTo: HTMLElement | null = null; | |
| $effect.pre(() => { | |
| if (typeof document === 'undefined' || !open) return; | |
| const activeElement = document.activeElement; | |
| if (activeElement instanceof HTMLElement) restoreFocusTo = activeElement; | |
| }); | |
| $effect(() => { | |
| if (typeof document === 'undefined' || !open || !lockScroll) return; | |
| function preventOutsideScroll(event: WheelEvent | TouchEvent) { | |
| const target = event.target; | |
| if (target instanceof Node && contentElement?.contains(target)) return; | |
| event.preventDefault(); | |
| } | |
| document.addEventListener('wheel', preventOutsideScroll, { passive: false }); | |
| document.addEventListener('touchmove', preventOutsideScroll, { passive: false }); | |
| return () => { | |
| document.removeEventListener('wheel', preventOutsideScroll); | |
| document.removeEventListener('touchmove', preventOutsideScroll); | |
| }; | |
| }); | |
| function handleCloseAutoFocus(event: Event) { | |
| event.preventDefault(); | |
| if (restoreFocusTo && document.contains(restoreFocusTo)) { | |
| restoreFocusTo.focus({ preventScroll: true }); | |
| } | |
| restoreFocusTo = null; | |
| } | |
| </script> | |
| <UISheet.Root bind:open> | |
| <UISheet.Content | |
| bind:ref={contentElement} | |
| {side} | |
| preventScroll={false} | |
| onCloseAutoFocus={handleCloseAutoFocus} | |
| class={cn( | |
| 'w-screen max-w-none overflow-y-auto overscroll-contain p-0 data-[side=right]:w-screen! data-[side=right]:max-w-none! sm:data-[side=right]:w-[90vw]! sm:data-[side=right]:max-w-none! lg:data-[side=right]:w-1/2!', | |
| contentClass | |
| )} | |
| > | |
| {#if header} | |
| {@render header()} | |
| {:else} | |
| <UISheet.Header class={cn('border-b p-4 pr-14 sm:p-5 sm:pr-16', headerClass)}> | |
| <UISheet.Title | |
| class={cn('min-w-0 font-mono text-lg font-semibold tracking-normal', titleClass)} | |
| > | |
| {#if title} | |
| {@render title()} | |
| {:else} | |
| <span class="block truncate">{titleText}</span> | |
| {/if} | |
| </UISheet.Title> | |
| {#if description} | |
| <UISheet.Description class="text-sm leading-6 text-muted-foreground"> | |
| {description} | |
| </UISheet.Description> | |
| {/if} | |
| </UISheet.Header> | |
| {/if} | |
| {@render children?.()} | |
| </UISheet.Content> | |
| </UISheet.Root> | |