add vitepress docs
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="tui-demo">
|
||||
<section class="stage">
|
||||
<div class="viewport">
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="meta">
|
||||
<div class="info">
|
||||
<p v-if="title" class="title" v-text="title" />
|
||||
<p v-if="description" class="desc" v-text="description" />
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button
|
||||
class="action"
|
||||
type="button"
|
||||
@click="handleCopy"
|
||||
v-text="copied ? '已复制' : '复制代码'"
|
||||
/>
|
||||
<button
|
||||
class="action"
|
||||
type="button"
|
||||
@click="toggleExpanded"
|
||||
v-text="expanded ? '收起代码' : '展开代码'"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-show="expanded" class="source">
|
||||
<div class="language-vue" v-html="sourceHtml" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, ref } from "vue";
|
||||
|
||||
/**
|
||||
* 预览容器。
|
||||
*
|
||||
* props 契约由 `@vitepress-demo-preview/plugin` 决定,它把 markdown 里的
|
||||
* `:::preview` 容器编译成 `<demo-preview>` 标签并传入下列属性:
|
||||
* - `code`:`encodeURI` 处理过的组件源码
|
||||
* - `showCode`:`encodeURIComponent` 处理过的高亮 HTML
|
||||
*
|
||||
* 这里刻意不依赖官方容器,也不引入额外 UI 库,只用原生元素配合 VitePress 的
|
||||
* CSS 变量,深色模式随之自动适配。
|
||||
*
|
||||
* 演示统一放在手机宽度的视口里:这些是移动端组件,按桌面宽度铺开会失去真实形态。
|
||||
* 需要调整宽度时改 `--tui-demo-viewport` 即可。
|
||||
*/
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 源码原文,经 encodeURI 编码。 */
|
||||
code?: string;
|
||||
/** 高亮后的源码 HTML,经 encodeURIComponent 编码。 */
|
||||
showCode?: string;
|
||||
/** 演示标题,来自 `:::preview` 后方第一段文字。 */
|
||||
title?: string;
|
||||
/** 演示描述,来自 `:::preview` 中 `||` 之后的内容。 */
|
||||
description?: string;
|
||||
}>(), {
|
||||
code: "",
|
||||
showCode: "",
|
||||
title: "",
|
||||
description: ""
|
||||
});
|
||||
|
||||
/** 源码区是否展开,默认收起以保持文档阅读节奏。 */
|
||||
const expanded = ref(false);
|
||||
/** 复制成功后的短暂反馈。 */
|
||||
const copied = ref(false);
|
||||
let copiedTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const sourceHtml = computed(() => decodeURIComponent(props.showCode));
|
||||
const sourceText = computed(() => decodeURIComponent(props.code));
|
||||
|
||||
/** 复制源码到剪贴板,clipboard 不可用时保持原按钮文案。 */
|
||||
async function handleCopy() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(sourceText.value);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
copied.value = true;
|
||||
if (copiedTimer !== null) {
|
||||
clearTimeout(copiedTimer);
|
||||
}
|
||||
copiedTimer = setTimeout(() => {
|
||||
copied.value = false;
|
||||
copiedTimer = null;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded.value = !expanded.value;
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (copiedTimer !== null) {
|
||||
clearTimeout(copiedTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tui-demo {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: .75rem;
|
||||
background: var(--vp-c-bg);
|
||||
|
||||
.stage {
|
||||
display: flex;
|
||||
padding: 1.5rem;
|
||||
justify-content: center;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
// 手机视口:移动组件在桌面宽度下会被拉伸,这里统一收到移动端宽度
|
||||
.viewport {
|
||||
width: 100%;
|
||||
max-width: var(--tui-demo-viewport, 26rem);
|
||||
padding: 1rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: .75rem;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .75rem 1.5rem;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.info {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: .875rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin: .25rem 0 0;
|
||||
font-size: .75rem;
|
||||
line-height: 1.5;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex: none;
|
||||
gap: .25rem;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: .25rem .5rem;
|
||||
font-size: .75rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: .25rem;
|
||||
color: var(--vp-c-text-2);
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-default-soft);
|
||||
}
|
||||
}
|
||||
|
||||
.source {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
|
||||
:deep(div[class*="language-"]) {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.tui-demo {
|
||||
.stage {
|
||||
padding: .75rem;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.meta {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding: .75rem 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user