enzostvs's picture
enzostvs HF Staff
improve UI
fe3e1db
<script lang="ts">
import Button from '$lib/components/ui/button/button.svelte';
import { Check, Copy, Eye, X } from '@lucide/svelte';
import { HighlightAuto } from 'svelte-highlight';
import { mode } from 'mode-watcher';
import githubDarkUrl from 'svelte-highlight/styles/github-dark.css?url';
import githubUrl from 'svelte-highlight/styles/github.css?url';
import Switch from '$lib/components/ui/switch/switch.svelte';
import * as Dialog from '$lib/components/ui/dialog/index.js';
let {
lang,
text,
className = 'my-3 relative rounded-lg border border-border/60 bg-muted/50'
}: { lang?: string; text: string; className?: string } = $props();
let copiedCode = $state(false);
let open = $state.raw(false);
async function copy(text: string) {
await navigator.clipboard.writeText(text);
copiedCode = true;
setTimeout(() => (copiedCode = false), 2000);
}
function hasStrictHtml5Doctype(input: string): boolean {
if (!input) return false;
const withoutBOM = input.replace(/^\uFEFF/, '');
const trimmed = withoutBOM.trimStart();
// Strict HTML5 doctype: <!doctype html> with optional whitespace before >
return /^<!doctype\s+html\s*>/i.test(trimmed);
}
function isSvgDocument(input: string): boolean {
const trimmed = input.trimStart();
return /^(?:<\?xml[^>]*>\s*)?(?:<!doctype\s+svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed);
}
$effect(() => {
const themeUrl = mode.current === 'dark' ? githubDarkUrl : githubUrl;
let link = document.getElementById('highlight-theme') as HTMLLinkElement | null;
if (!link) {
link = document.createElement('link');
link.id = 'highlight-theme';
link.rel = 'stylesheet';
document.head.appendChild(link);
}
link.href = themeUrl;
});
let canShowPreview = $derived(
hasStrictHtml5Doctype(text) || isSvgDocument(text) || lang == 'svg' || lang == 'xml'
);
// svelte-ignore state_referenced_locally
let showPreview = $state.raw(canShowPreview);
</script>
<div class="overflow-hidden {className}">
{#if lang}
<div
class="flex items-center justify-between border-b border-border/60 bg-muted px-3 py-1.5 dark:bg-accent/30"
>
<span class="font-mono text-xs text-muted-foreground">
{#if showPreview}
Live Preview
{:else}
{lang}
{/if}
</span>
{#if canShowPreview}
<div class="flex items-center gap-1.5">
{#if showPreview}
<Button variant="outline" size="2xs" onclick={() => (open = true)}>
<Eye class="size-3.5" />
Open
</Button>
{/if}
<p class="font-mono text-[10px] text-muted-foreground">
Show {showPreview ? 'Preview' : 'Code'}
</p>
<Switch bind:checked={showPreview} />
</div>
{/if}
</div>
{/if}
{#if showPreview}
<div class="group relative">
<iframe
srcDoc={text}
class="h-[500px] w-full"
title="Preview"
sandbox="allow-scripts allow-popups"
referrerpolicy="no-referrer"
></iframe>
</div>
{:else}
<div class="group relative">
<HighlightAuto code={text} class="font-mono text-xs! leading-relaxed" />
<Button
variant="outline"
class="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
size="icon-xs"
onclick={() => copy(text)}
>
{#if copiedCode}
<Check class="size-3.5 text-green-500" />
{:else}
<Copy class="size-3.5" />
{/if}
</Button>
</div>
{/if}
</div>
<Dialog.Root bind:open>
<Dialog.Content
class="max-w-[90dvw]! gap-0! space-y-0! border-none! p-0!"
showCloseButton={false}
>
<Dialog.Description class="relative mt-0 pt-0!">
<Button
variant="outline"
class="absolute top-2 right-2"
size="icon-xs"
onclick={() => (open = false)}
>
<X class="size-3.5" />
</Button>
<iframe
srcDoc={text}
class="h-[90dvh] w-full"
title="Preview"
sandbox="allow-scripts allow-popups"
referrerpolicy="no-referrer"
></iframe>
</Dialog.Description>
</Dialog.Content>
</Dialog.Root>