@antdv-next/docs-plugins 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { CSSProperties } from 'vue'
3
- import type { DemoExtraFile, DemoModule, DemoSourceData } from '../../demo/types'
4
- import { CheckOutlined, CodeOutlined, CopyOutlined, EditOutlined, ThunderboltOutlined } from '@antdv-next/icons'
5
- import { aquaBlue, atomDark } from '@codesandbox/sandpack-themes'
6
- import { useClipboard, useDebounceFn } from '@vueuse/core'
7
- import { Alert, Flex, Skeleton, Spin, Tabs, Tooltip } from 'antdv-next'
8
- import { createStyles } from 'antdv-style'
9
- import { loadDemo } from 'virtual:demos'
10
- import { computed, defineAsyncComponent, markRaw, nextTick, onBeforeUnmount, shallowRef, watch } from 'vue'
11
- import { useRoute, useRouter } from 'vue-router'
12
- import { getDemoId } from '../../demo/get-demo-id'
13
- import CodeEditorBridge from './code-editor-bridge.vue'
14
- import { compileSfcSource } from './compile-sfc'
15
- import { resolveLabel, useDemoContext } from './context'
16
- import ExpandIcon from './expand-icon.vue'
17
- import ExternalLinkIcon from './external-link-icon.vue'
2
+ import { ThemeProvider, useTheme } from 'antdv-style'
3
+ import DemoPanel from './demo-panel.vue'
18
4
 
19
5
  defineOptions({
20
6
  name: 'Demo',
@@ -39,644 +25,28 @@ const props = withDefaults(defineProps<{
39
25
  debug: false,
40
26
  })
41
27
 
42
- const TabPane = Tabs.TabPane
43
-
44
- const context = useDemoContext()
45
-
46
- const useStyles = createStyles(({ token }) => ({
47
- root: {
48
- 'breakInside': 'avoid',
49
- 'display': 'flow-root',
50
- 'overflow': 'hidden',
51
- 'position': 'relative',
52
- 'boxSizing': 'border-box',
53
- 'border': `1px solid ${token.colorSplit}`,
54
- 'borderRadius': token.borderRadiusLG,
55
- 'background': token.colorBgContainer,
56
- 'transition': 'border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease',
57
- 'margin': '16px 0',
58
- '&.border-primary': {
59
- borderColor: token.colorPrimary,
60
- boxShadow: `0 0 0 3px color-mix(in srgb, ${token.colorPrimary} 12%, transparent)`,
61
- },
62
- '&.ant-doc-demo-box-debug': {
63
- borderColor: '#d3adf7',
64
- },
65
- '& .ant-doc-demo-box-demo': {
66
- padding: '24px',
67
- borderBottom: `1px solid ${token.colorSplit}`,
68
- borderRadius: '8px 8px 0 0',
69
- background: token.colorBgContainer,
70
- },
71
- '& .ant-doc-demo-box-skeleton': {
72
- minHeight: 160,
73
- },
74
- '&.ant-doc-demo-box-simplify': {
75
- borderRadius: 0,
76
- background: 'transparent',
77
- },
78
- '&.ant-doc-demo-box-simplify .ant-doc-demo-box-demo': {
79
- padding: 0,
80
- borderBottom: 0,
81
- background: 'transparent',
82
- },
83
- '& .ant-doc-demo-box-meta.markdown': {
84
- position: 'relative',
85
- width: '100%',
86
- fontSize: 14,
87
- borderRadius: '0 0 6px 6px',
88
- transition: 'background-color 0.4s',
89
- },
90
- '& .ant-doc-demo-box-meta-description': {
91
- padding: '18px 12px 24px',
92
- },
93
- '& .ant-doc-demo-box-meta-description p': {
94
- margin: 0,
95
- },
96
- '& .ant-doc-demo-box-title': {
97
- position: 'absolute',
98
- top: -16,
99
- marginLeft: 16,
100
- padding: '1px 8px',
101
- borderRadius: '6px 6px 0 0',
102
- backgroundColor: token.colorBgContainer,
103
- transition: 'background-color 0.4s',
104
- },
105
- '& .ant-doc-demo-box-title a': {
106
- color: token.colorText,
107
- textDecoration: 'none',
108
- fontSize: 16,
109
- fontWeight: 500,
110
- },
111
- '& .ant-doc-demo-box-actions': {
112
- display: 'flex',
113
- justifyContent: 'center',
114
- padding: '12px 0',
115
- borderTop: `1px dashed ${token.colorSplit}`,
116
- opacity: 0.7,
117
- transition: 'opacity 0.3s',
118
- },
119
- '&:hover .ant-doc-demo-box-actions': {
120
- opacity: 1,
121
- },
122
- '& .ant-doc-demo-box-code-action': {
123
- display: 'inline-flex',
124
- alignItems: 'center',
125
- justifyContent: 'center',
126
- width: 16,
127
- height: 16,
128
- border: 0,
129
- padding: 0,
130
- background: 'transparent',
131
- color: token.colorTextSecondary,
132
- cursor: 'pointer',
133
- transition: 'color 0.24s ease',
134
- },
135
- '& .ant-doc-demo-box-code-action:hover': {
136
- color: token.colorPrimary,
137
- },
138
- '& .ant-doc-demo-box-edit-icon': {
139
- marginInlineStart: 4,
140
- color: token.colorTextTertiary,
141
- },
142
- '& .ant-doc-demo-box-code': {
143
- position: 'relative',
144
- lineHeight: 2,
145
- padding: `${token.paddingSM}px ${token.padding}px`,
146
- },
147
- '& .ant-doc-demo-box-code-loading': {
148
- display: 'flex',
149
- justifyContent: 'center',
150
- paddingBlock: token.paddingLG,
151
- },
152
- '& .ant-doc-demo-box-compile-error': {
153
- margin: 0,
154
- padding: '8px 16px',
155
- background: token.colorErrorBg,
156
- color: token.colorError,
157
- fontSize: 12,
158
- lineHeight: 1.6,
159
- whiteSpace: 'pre-wrap',
160
- },
161
- '& .ant-doc-demo-box-code-tabs': {
162
- borderTop: `1px dashed ${token.colorSplit}`,
163
- },
164
- '& .ant-doc-demo-box-code-tabs .ant-tabs-nav': {
165
- marginBottom: 0,
166
- },
167
- '& .ant-doc-demo-box-code-tabs .ant-tabs-tab': {
168
- fontSize: 12,
169
- },
170
- '& .ant-doc-demo-box-code-copy': {
171
- position: 'absolute',
172
- top: 10,
173
- right: 10,
174
- zIndex: 1,
175
- display: 'inline-flex',
176
- alignItems: 'center',
177
- justifyContent: 'center',
178
- width: 24,
179
- height: 24,
180
- border: 0,
181
- padding: 0,
182
- background: 'transparent',
183
- color: token.colorIcon,
184
- cursor: 'pointer',
185
- },
186
- '& .ant-doc-demo-box-code-copied': {
187
- color: token.colorSuccess,
188
- },
189
- '& .ant-doc-demo-box-collapse-btn': {
190
- display: 'flex',
191
- alignItems: 'center',
192
- justifyContent: 'center',
193
- gap: 8,
194
- padding: '8px 0',
195
- borderTop: `1px dashed ${token.colorSplit}`,
196
- color: token.colorTextSecondary,
197
- cursor: 'pointer',
198
- },
199
- },
200
- }))
201
-
202
- function t(key: Parameters<typeof resolveLabel>[1]) {
203
- return resolveLabel(context, key)
204
- }
205
-
206
- const { styles } = useStyles()
207
-
208
- // Debug demos are visible while developing but stripped from the production docs.
209
- const hidden = computed(() => Boolean(props.debug) && import.meta.env.PROD)
210
-
211
- // demo 模块经 virtual:demos 注册表懒加载(代码分割,按需拉取)
212
- const demo = shallowRef<DemoModule | undefined>(undefined)
213
- let demoLoadToken = 0
214
-
215
- watch(() => props.src, (newSrc) => {
216
- const token = ++demoLoadToken
217
- demo.value = undefined
218
- if (!newSrc)
219
- return
220
- loadDemo(newSrc).then((mod) => {
221
- if (token === demoLoadToken && mod)
222
- demo.value = mod
223
- }).catch(() => {})
224
- }, { immediate: true })
225
-
226
- // 按需加载的源码数据
227
- const sourceData = shallowRef<DemoSourceData | null>(null)
228
- const sourceLoading = shallowRef(false)
229
- const sourceLoadError = shallowRef<Error | null>(null)
230
- let sourceLoadPromise: Promise<void> | null = null
231
- let sourceAbortController: AbortController | null = null
232
- // HMR 发生在面板收起期间时标记过期,下次展开时重新加载
233
- let sourceStale = false
234
-
235
- function releaseSource() {
236
- sourceAbortController?.abort()
237
- sourceAbortController = null
238
- sourceData.value = null
239
- sourceLoadError.value = null
240
- sourceLoadPromise = null
241
- sourceLoading.value = false
242
- sourceStale = false
243
- }
244
-
245
- async function ensureSourceLoaded() {
246
- // 如果源码已过期(HMR 发生在收起期间),先释放旧数据
247
- if (sourceStale)
248
- releaseSource()
249
- if (sourceData.value || !demo.value)
250
- return
251
- if (sourceLoadPromise)
252
- return sourceLoadPromise
253
-
254
- const currentDemo = demo.value
255
- const abortController = new AbortController()
256
- sourceAbortController = abortController
257
- sourceLoading.value = true
258
- sourceLoadError.value = null
259
-
260
- const request = currentDemo
261
- .loadSource(abortController.signal)
262
- .then((data) => {
263
- if (demo.value === currentDemo && !abortController.signal.aborted)
264
- sourceData.value = data
265
- })
266
- .catch((error) => {
267
- if (abortController.signal.aborted)
268
- return
269
- const loadError
270
- = error instanceof Error ? error : new Error(String(error))
271
- if (demo.value === currentDemo)
272
- sourceLoadError.value = loadError
273
- throw loadError
274
- })
275
- .finally(() => {
276
- if (sourceLoadPromise === request) {
277
- sourceAbortController = null
278
- sourceLoadPromise = null
279
- sourceLoading.value = false
280
- }
281
- })
282
-
283
- sourceLoadPromise = request
284
- return request
285
- }
286
-
287
- const route = useRoute()
288
- const router = useRouter()
289
-
290
- const hasJsSource = computed(() => {
291
- const jsSource = sourceData.value?.jsSource?.trim()
292
- return Boolean(jsSource)
293
- })
294
-
295
- const extraFiles = computed<DemoExtraFile[]>(() => sourceData.value?.extraFiles ?? [])
296
-
297
- const codeTabKeys = computed(() => {
298
- const keys: string[] = ['ts']
299
- if (hasJsSource.value)
300
- keys.push('js')
301
- for (const file of extraFiles.value)
302
- keys.push(file.name)
303
- return keys
304
- })
305
-
306
- // ts/js 切换:每个 Demo 实例维护独立的偏好,初始值取自站点提供的持久化
307
- // (不通过 computed 依赖全局状态,避免一个 demo 切换导致所有 demo 同步切换)
308
- const localTsJs = shallowRef<'ts' | 'js'>(context.preferredCodeType?.get() ?? 'ts')
309
- const localCodeKey = shallowRef<string | null>(null)
310
-
311
- const activeCodeType = computed<string>({
312
- get() {
313
- if (localCodeKey.value && codeTabKeys.value.includes(localCodeKey.value))
314
- return localCodeKey.value
315
- const preferred = localTsJs.value
316
- if (codeTabKeys.value.includes(preferred))
317
- return preferred
318
- return 'ts'
319
- },
320
- set(value) {
321
- if (value === 'ts' || value === 'js') {
322
- localTsJs.value = value
323
- context.preferredCodeType?.set(value)
324
- localCodeKey.value = null
325
- }
326
- else {
327
- localCodeKey.value = value
328
- }
329
- },
330
- })
331
-
332
- const activeExtraFile = computed(() =>
333
- extraFiles.value.find(file => file.name === activeCodeType.value),
334
- )
335
-
336
- /** 将 demo 相对导入路径映射为 sandpack 虚拟文件路径 */
337
- function extraFileToSandpackPath(name: string) {
338
- return `/src/${name.replace(/^(\.\.?\/)+/, '')}`
339
- }
340
-
341
- /** 代码 tab 显示名(去掉 ./ 前缀) */
342
- function displayFileName(name: string) {
343
- return name.replace(/^(\.\.?\/)+/, '')
344
- }
345
-
346
- const mainSourceCode = computed(() => {
347
- if (activeCodeType.value === 'js')
348
- return sourceData.value?.jsSource ?? sourceData.value?.source ?? ''
349
- return sourceData.value?.source ?? ''
350
- })
351
-
352
- // 编辑器当前展示的源码(主 demo 或伴生文件)
353
- const activeSourceCode = computed(() => {
354
- if (activeExtraFile.value)
355
- return activeExtraFile.value.code
356
- return mainSourceCode.value
357
- })
358
-
359
- const description = computed(() => {
360
- const locales = demo.value?.locales ?? {}
361
- const locale = context.locale?.() ?? 'zh-CN'
362
- const localeData = locales[locale] || locales['zh-CN'] || locales['en-US'] || Object.values(locales)[0] || {}
363
- return localeData?.html ?? ''
364
- })
365
-
366
- const component = computed(() => typeof demo.value?.component === 'function' ? defineAsyncComponent(demo.value.component) : demo.value?.component)
367
- const id = computed(() => {
368
- if (!props.src)
369
- return ''
370
- return getDemoId(props.src)
371
- })
372
- const showCode = shallowRef(false)
373
- const liveComponent = shallowRef<any>(null)
374
- const compileError = shallowRef<string | null>(null)
375
- const currentCode = shallowRef<string | null>(null)
376
- const editorBridgeRef = shallowRef<{ resetCode: (code: string) => void }>()
377
-
378
- function handleShowCode() {
379
- showCode.value = !showCode.value
380
- if (!showCode.value) {
381
- // 收起时只重置实时编辑状态,保留源码以便快速重新展开
382
- liveComponent.value = null
383
- compileError.value = null
384
- currentCode.value = null
385
- }
386
- else {
387
- // 展开时按需加载源码(ensureSourceLoaded 内部会处理过期重载)
388
- void ensureSourceLoaded().catch(() => {})
389
- }
390
- }
391
-
392
- // 切换 demo 时释放旧源码并重置多文件 tab
393
- watch(demo, () => {
394
- localCodeKey.value = null
395
- releaseSource()
396
- }, { flush: 'sync' })
397
-
398
- // 展开代码面板时触发加载(收起时不释放,保留源码)
399
- watch([showCode, demo], ([visible, currentDemo]) => {
400
- if (!visible)
401
- return
402
- if (currentDemo)
403
- void ensureSourceLoaded().catch(() => {})
404
- })
405
-
406
- // HMR 触发的 sourceVersion 变化:展开时立即重载,收起时标记过期
407
- watch(
408
- () => demo.value?.sourceVersion,
409
- (version, previousVersion) => {
410
- if (version === previousVersion)
411
- return
412
- // 如果用户正在编辑,暂缓重载,避免覆盖编辑内容
413
- if (currentCode.value !== null || liveComponent.value !== null) {
414
- sourceStale = true
415
- return
416
- }
417
- if (showCode.value) {
418
- releaseSource()
419
- void ensureSourceLoaded().catch(() => {})
420
- }
421
- else if (sourceData.value) {
422
- sourceStale = true
423
- }
424
- },
425
- )
426
-
427
- onBeforeUnmount(releaseSource)
428
-
429
- // Reset live component and editor code when tab changes
430
- watch(activeCodeType, () => {
431
- liveComponent.value = null
432
- compileError.value = null
433
- currentCode.value = null
434
- // 等 sandpack 完成 activeFile 切换后再重置内容,避免写入错误的文件
435
- nextTick(() => {
436
- editorBridgeRef.value?.resetCode(activeSourceCode.value)
437
- })
438
- })
439
-
440
- const debouncedCompile = useDebounceFn(async (newCode: string) => {
441
- // 伴生文件 tab 不参与主 demo 的实时编译
442
- if (activeExtraFile.value)
443
- return
444
- // Code matches original source (e.g. after tab switch reset), skip compilation
445
- if (newCode === mainSourceCode.value) {
446
- liveComponent.value = null
447
- compileError.value = null
448
- return
449
- }
450
- const { component: comp, error } = await compileSfcSource(newCode, context.modules)
451
- if (comp) {
452
- liveComponent.value = markRaw(comp)
453
- compileError.value = null
454
- }
455
- else {
456
- compileError.value = error
457
- }
458
- }, 300)
459
-
460
- function handleCodeChange(newCode: string) {
461
- currentCode.value = newCode
462
- debouncedCompile(newCode)
463
- }
464
-
465
- const sandpackTheme = computed(() => context.isDark?.() ? atomDark : aquaBlue)
466
-
467
- const sandpackFiles = computed(() => {
468
- const files: Record<string, string> = {
469
- '/src/App.vue': mainSourceCode.value,
470
- }
471
- for (const file of extraFiles.value) {
472
- files[extraFileToSandpackPath(file.name)] = file.code
473
- }
474
- return files
475
- })
476
-
477
- // 当前激活的 sandpack 文件(多文件 tab 时切换)
478
- const sandpackActiveFile = computed(() => {
479
- if (activeExtraFile.value)
480
- return extraFileToSandpackPath(activeExtraFile.value.name)
481
- return '/src/App.vue'
482
- })
483
-
484
- const sandpackOptions = computed(() => ({
485
- autorun: false,
486
- activeFile: sandpackActiveFile.value,
487
- }))
488
-
489
- const active = computed(() => route.hash === `#${id.value}`)
490
- function handleScroll(e: Event) {
491
- e.preventDefault()
492
- e.stopPropagation()
493
- router.push({
494
- path: route.path,
495
- hash: `#${id.value}`,
496
- })
497
- }
498
-
499
- const titleRef = shallowRef<HTMLElement>()
500
-
501
- async function handleStackBlitz() {
502
- if (!context.openStackBlitz)
503
- return
28
+ // antdv-style useStyles 需要 <ThemeProvider> 祖先;站点未提供时由组件自给,
29
+ // 提供时则直接复用(避免嵌套 provider 造成 token 漂移)
30
+ function hasThemeProvider() {
504
31
  try {
505
- await ensureSourceLoaded()
32
+ useTheme()
33
+ return true
506
34
  }
507
35
  catch {
508
- showCode.value = true
509
- return
510
- }
511
- if (mainSourceCode.value) {
512
- context.openStackBlitz({
513
- title: titleRef.value?.textContent || 'Demo',
514
- code: mainSourceCode.value,
515
- })
36
+ return false
516
37
  }
517
38
  }
518
39
 
519
- function handleOpenPlayground() {
520
- if (!context.openPlayground)
521
- return
522
- context.openPlayground(mainSourceCode.value)
523
- }
524
-
525
- const demoStyle = computed(() => {
526
- const style: CSSProperties = {}
527
- if (props.compact) {
528
- style.padding = '0px'
529
- style.overflow = 'hidden'
530
- }
531
- if (props.background === 'grey') {
532
- style.backgroundColor = 'var(--ant-color-bg-layout)'
533
- }
534
- return style
535
- })
536
-
537
- const copySource = computed(() => currentCode.value ?? activeSourceCode.value)
538
-
539
- const { copied, copy } = useClipboard({
540
- source: copySource,
541
- legacy: true,
542
- })
543
-
544
- const cls = computed(() => {
545
- const cls: string[] = []
546
- if (active.value) {
547
- cls.push('border-primary')
548
- }
549
- if (props.simplify) {
550
- cls.push('ant-doc-demo-box-simplify')
551
- }
552
- if (props.debug) {
553
- cls.push('ant-doc-demo-box-debug')
554
- }
555
- return cls
556
- })
40
+ const wrap = hasThemeProvider()
557
41
  </script>
558
42
 
559
43
  <template>
560
- <section v-if="!hidden" :id="id" class="ant-doc-demo-box" :class="[styles.root, cls]">
561
- <template v-if="simplify">
562
- <section class="ant-doc-demo-box-demo vp-raw" :style="demoStyle">
563
- <Skeleton v-if="!demo" active :paragraph="{ rows: 5 }" />
564
- <component :is="liveComponent || component" v-else-if="liveComponent || demo?.component" />
565
- </section>
566
- </template>
567
- <template v-else>
568
- <!-- Preview area: always visible, shows live-compiled or original component -->
569
- <section v-if="!iframe" class="ant-doc-demo-box-demo vp-raw" :style="demoStyle">
570
- <Skeleton v-if="!demo" active :paragraph="{ rows: 5 }" />
571
- <Suspense v-else>
572
- <component :is="liveComponent || component" v-if="liveComponent || demo?.component" />
573
- <template #fallback>
574
- <Skeleton active :paragraph="{ rows: 5 }" />
575
- </template>
576
- </Suspense>
577
- </section>
578
- <template v-else-if="context.iframeRenderer">
579
- <component :is="context.iframeRenderer(id, props.iframe)" />
580
- </template>
581
- <!-- Compile error hint -->
582
- <div v-if="compileError && showCode" class="ant-doc-demo-box-compile-error">
583
- <pre>{{ compileError }}</pre>
584
- </div>
585
- <!-- Meta: title, description, actions -->
586
- <section class="ant-doc-demo-box-meta markdown">
587
- <div class="ant-doc-demo-box-title">
588
- <a ref="titleRef" :href="`#${id}`" @click="handleScroll">
589
- <slot />
590
- </a>
591
- <a target="_blank" rel="noopener" class="ant-doc-demo-box-edit-icon">
592
- <EditOutlined />
593
- </a>
594
- </div>
595
- <div v-if="description" class="ant-doc-demo-box-meta-description">
596
- <div v-html="description" />
597
- </div>
598
- <Flex class="ant-doc-demo-box-actions" wrap gap="middle">
599
- <a v-if="context.openStackBlitz" class="ant-doc-demo-box-code-action" @click="handleStackBlitz">
600
- <Tooltip :title="t('action.stackblitz')">
601
- <ThunderboltOutlined />
602
- </Tooltip>
603
- </a>
604
- <a
605
- v-if="context.demoPageUrl"
606
- class="ant-doc-demo-box-code-action"
607
- :href="context.demoPageUrl(id)"
608
- target="_blank"
609
- rel="noopener"
610
- >
611
- <Tooltip :title="t('action.externalLink')">
612
- <ExternalLinkIcon />
613
- </Tooltip>
614
- </a>
615
- <div v-if="context.openPlayground" class="ant-doc-demo-box-code-action" @click="handleOpenPlayground">
616
- <Tooltip :title="t('action.openPlayground')">
617
- <CodeOutlined />
618
- </Tooltip>
619
- </div>
620
- <div class="ant-doc-demo-box-expand-icon ant-doc-demo-box-code-action" @click="handleShowCode">
621
- <Tooltip :title="t(`action.${showCode ? 'expandedCode' : 'expandCode'}`)">
622
- <ExpandIcon :expanded="showCode" />
623
- </Tooltip>
624
- </div>
625
- </Flex>
626
- </section>
627
- <!-- Code editor (only when expanded) -->
628
- <template v-if="showCode">
629
- <!-- 加载中 -->
630
- <div v-if="sourceLoading" class="ant-doc-demo-box-code-loading">
631
- <Spin />
632
- </div>
633
- <!-- 加载失败 -->
634
- <div v-else-if="sourceLoadError" class="ant-doc-demo-box-code">
635
- <Alert type="error" :message="t('action.loadError')" />
636
- </div>
637
- <!-- 正常展示 -->
638
- <template v-else>
639
- <div class="ant-doc-demo-box-code-tabs">
640
- <Tabs
641
- v-model:active-key="activeCodeType"
642
- centered
643
- size="small"
644
- >
645
- <TabPane key="ts" :tab="t('type.typescript')" />
646
- <TabPane v-if="hasJsSource" key="js" :tab="t('type.javascript')" />
647
- <TabPane
648
- v-for="file in extraFiles"
649
- :key="file.name"
650
- :tab="displayFileName(file.name)"
651
- />
652
- </Tabs>
653
- </div>
654
- <div class="ant-doc-demo-box-code">
655
- <Tooltip :title="t(`action.${copied ? 'copied' : 'copy'}`)">
656
- <div class="ant-doc-demo-box-code-copy" :class="copied ? 'ant-doc-demo-box-code-copied' : ''" @click="copy()">
657
- <CopyOutlined v-if="!copied" />
658
- <CheckOutlined v-else />
659
- </div>
660
- </Tooltip>
661
- <SandpackProvider
662
- template="vite-vue-ts"
663
- :files="sandpackFiles"
664
- :theme="sandpackTheme"
665
- :options="sandpackOptions"
666
- >
667
- <CodeEditorBridge
668
- ref="editorBridgeRef"
669
- @update:code="handleCodeChange"
670
- />
671
- </SandpackProvider>
672
- </div>
673
- <!-- Collapse button at bottom -->
674
- <div class="ant-doc-demo-box-collapse-btn" @click="handleShowCode">
675
- <ExpandIcon :expanded="showCode" />
676
- <span>{{ t('action.expandedCode') }}</span>
677
- </div>
678
- </template>
679
- </template>
680
- </template>
681
- </section>
44
+ <ThemeProvider v-if="!wrap">
45
+ <DemoPanel v-bind="props">
46
+ <slot />
47
+ </DemoPanel>
48
+ </ThemeProvider>
49
+ <DemoPanel v-else v-bind="props">
50
+ <slot />
51
+ </DemoPanel>
682
52
  </template>