63 lines
1.6 KiB
Vue
63 lines
1.6 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, watchEffect } from 'vue'
|
|
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
|
import { highlightToHtml, useShikiHighlighter } from '@/composables/useShikiHighlighter'
|
|
|
|
const props = defineProps<{
|
|
code: string
|
|
lang: string
|
|
label?: string
|
|
}>()
|
|
|
|
const { copied, copy } = useCopyToClipboard()
|
|
|
|
const buttonLabel = computed(() => (copied.value ? 'Copied' : 'Copy'))
|
|
const buttonIcon = computed(() => (copied.value ? 'i-lucide-check' : 'i-lucide-copy'))
|
|
|
|
const highlightedHtml = ref<string | null>(null)
|
|
|
|
watchEffect(async () => {
|
|
const highlighter = await useShikiHighlighter()
|
|
highlightedHtml.value = highlightToHtml(highlighter, props.code, props.lang)
|
|
})
|
|
|
|
function onCopy() {
|
|
copy(props.code)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-elevated">
|
|
<div class="flex h-8 items-center justify-between border-b border-default pr-2 pl-4">
|
|
<span class="font-mono text-xs leading-tight font-medium text-muted">{{
|
|
label ?? lang
|
|
}}</span>
|
|
<UButton
|
|
:icon="buttonIcon"
|
|
color="neutral"
|
|
variant="ghost"
|
|
size="xs"
|
|
:aria-label="`Copy ${label ?? lang} example`"
|
|
aria-live="polite"
|
|
class="gap-1.5 px-1.5 font-medium text-toned"
|
|
:ui="{ leadingIcon: 'size-3.5' }"
|
|
@click="onCopy"
|
|
>
|
|
{{ buttonLabel }}
|
|
</UButton>
|
|
</div>
|
|
<div
|
|
v-if="highlightedHtml"
|
|
tabindex="0"
|
|
class="shiki-block overflow-x-auto px-4 py-3.5 font-mono text-code whitespace-pre"
|
|
v-html="highlightedHtml"
|
|
/>
|
|
<pre
|
|
v-else
|
|
tabindex="0"
|
|
class="overflow-x-auto px-4 py-3.5 font-mono text-code whitespace-pre text-default"
|
|
>{{ code }}</pre
|
|
>
|
|
</div>
|
|
</template>
|