130 lines
3.1 KiB
Vue
130 lines
3.1 KiB
Vue
<script setup>
|
|
import { onMounted, ref } from "vue";
|
|
|
|
const props = defineProps({
|
|
code: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
language: {
|
|
type: String,
|
|
default: "markup",
|
|
validator: (value) =>
|
|
[
|
|
"markup",
|
|
"javascript",
|
|
"typescript",
|
|
"vue",
|
|
"html",
|
|
"jsx",
|
|
"tsx",
|
|
"css",
|
|
"scss",
|
|
].includes(value),
|
|
},
|
|
showCopy: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
initiallyExpanded: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
toggleLabel: {
|
|
type: String,
|
|
default: "View Code",
|
|
},
|
|
});
|
|
|
|
const copied = ref(false);
|
|
const codeBlockRef = ref(null);
|
|
const isExpanded = ref(props.initiallyExpanded);
|
|
|
|
const copyCode = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(props.code);
|
|
copied.value = true;
|
|
setTimeout(() => {
|
|
copied.value = false;
|
|
}, 2000);
|
|
} catch (err) {
|
|
console.error("Failed to copy code:", err);
|
|
}
|
|
};
|
|
|
|
const toggleCodeBlock = () => {
|
|
isExpanded.value = !isExpanded.value;
|
|
};
|
|
|
|
onMounted(async () => {
|
|
if (import.meta.client) {
|
|
const Prism = await import("prismjs");
|
|
await import("prismjs/themes/prism-tomorrow.css");
|
|
await import("prismjs/components/prism-javascript");
|
|
await import("prismjs/components/prism-typescript");
|
|
await import("prismjs/components/prism-jsx");
|
|
await import("prismjs/components/prism-tsx");
|
|
await import("prismjs/components/prism-markup");
|
|
await import("prismjs/components/prism-css");
|
|
await import("prismjs/components/prism-scss");
|
|
|
|
if (codeBlockRef.value) {
|
|
Prism.default.highlightElement(codeBlockRef.value);
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="my-6 w-full rounded-md border">
|
|
<!-- Header -->
|
|
<div class="flex items-center border-b">
|
|
<!-- Tab-like buttons -->
|
|
<div class="flex">
|
|
<button
|
|
@click="toggleCodeBlock"
|
|
class="flex h-10 items-center gap-2 border-r px-4 text-sm font-medium transition-colors hover:bg-muted"
|
|
:class="[
|
|
isExpanded ? 'bg-muted text-foreground' : 'text-muted-foreground',
|
|
]"
|
|
>
|
|
Code
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Copy button -->
|
|
<div class="ml-auto pr-2">
|
|
<button
|
|
v-if="showCopy"
|
|
@click="copyCode"
|
|
class="flex h-8 items-center gap-2 rounded-md px-3 text-sm transition-colors hover:bg-muted"
|
|
>
|
|
<span
|
|
v-if="!copied"
|
|
class="flex items-center gap-1 text-muted-foreground"
|
|
>
|
|
<Icon name="lucide:copy" class="h-4 w-4" />
|
|
Copy
|
|
</span>
|
|
<span v-else class="flex items-center gap-1 text-success">
|
|
<Icon name="lucide:check" class="h-4 w-4" />
|
|
Copied!
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Code Block -->
|
|
<div
|
|
v-show="isExpanded"
|
|
class="relative max-h-[650px] overflow-auto bg-muted"
|
|
>
|
|
<pre
|
|
class="overflow-x-auto py-4 text-[13px] leading-6"
|
|
><code :class="`language-${language} px-4`" ref="codeBlockRef">{{ code }}</code></pre>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|