File size: 2,919 Bytes
336b102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6afb12
 
 
 
 
 
 
 
 
336b102
 
 
c6afb12
 
 
 
 
336b102
c6afb12
 
336b102
 
c6afb12
 
336b102
 
c6afb12
 
 
 
 
 
 
 
 
336b102
 
 
 
c6afb12
336b102
c6afb12
 
336b102
74473f3
336b102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<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>