1<script lang="ts">
2 import { Dialog as DialogPrimitive } from 'bits-ui';
3 import XIcon from '@lucide/svelte/icons/x';
4
5 interface Props {
6 open: boolean;
7 code: string;
8 language: string;
9 onOpenChange?: (open: boolean) => void;
10 }
11
12 let { open = $bindable(), code, language, onOpenChange }: Props = $props();
13
14 let iframeRef = $state<HTMLIFrameElement | null>(null);
15
16 $effect(() => {
17 if (!iframeRef) return;
18
19 if (open) {
20 iframeRef.srcdoc = code;
21 } else {
22 iframeRef.srcdoc = '';
23 }
24 });
25
26 function handleOpenChange(nextOpen: boolean) {
27 open = nextOpen;
28 onOpenChange?.(nextOpen);
29 }
30</script>
31
32<DialogPrimitive.Root {open} onOpenChange={handleOpenChange}>
33 <DialogPrimitive.Portal>
34 <DialogPrimitive.Overlay class="code-preview-overlay" />
35
36 <DialogPrimitive.Content class="code-preview-content">
37 <iframe
38 bind:this={iframeRef}
39 title="Preview {language}"
40 sandbox="allow-scripts"
41 class="code-preview-iframe"
42 ></iframe>
43
44 <DialogPrimitive.Close
45 class="code-preview-close absolute top-4 right-4 border-none bg-transparent text-white opacity-70 mix-blend-difference transition-opacity hover:opacity-100 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-8"
46 aria-label="Close preview"
47 >
48 <XIcon />
49 <span class="sr-only">Close preview</span>
50 </DialogPrimitive.Close>
51 </DialogPrimitive.Content>
52 </DialogPrimitive.Portal>
53</DialogPrimitive.Root>
54
55<style lang="postcss">
56 :global(.code-preview-overlay) {
57 position: fixed;
58 inset: 0;
59 background-color: transparent;
60 z-index: 100000;
61 }
62
63 :global(.code-preview-content) {
64 position: fixed;
65 inset: 0;
66 top: 0 !important;
67 left: 0 !important;
68 width: 100dvw;
69 height: 100dvh;
70 margin: 0;
71 padding: 0;
72 border: none;
73 border-radius: 0;
74 background-color: transparent;
75 box-shadow: none;
76 display: block;
77 overflow: hidden;
78 transform: none !important;
79 z-index: 100001;
80 }
81
82 :global(.code-preview-iframe) {
83 display: block;
84 width: 100dvw;
85 height: 100dvh;
86 border: 0;
87 }
88
89 :global(.code-preview-close) {
90 position: absolute;
91 z-index: 100002;
92 }
93</style>