frappe-ui 0.1.225 → 0.1.226
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.
- package/package.json +1 -1
- package/src/components/Checkbox/Checkbox.vue +1 -0
- package/src/components/Combobox/Combobox.vue +1 -0
- package/src/components/FormControl/FormControl.story.vue +2 -2
- package/src/components/FormControl/FormControl.vue +10 -0
- package/src/components/FormControl/types.ts +1 -1
- package/src/components/Tabs/Tabs.story.vue +12 -9
- package/src/components/Tabs/Tabs.vue +61 -54
- package/src/components/Tabs/types.ts +11 -0
- package/src/components/TextEditor/link-extension.ts +1 -1
- package/src/index.ts +0 -2
- package/vite/index.js +16 -0
- package/src/components/Tabs/TabList.vue +0 -82
- package/src/components/Tabs/TabPanel.vue +0 -17
- package/src/components/Tabs/Tabs.story.md +0 -97
package/package.json
CHANGED
|
@@ -54,10 +54,10 @@ const variants = ['subtle', 'outline']
|
|
|
54
54
|
/>
|
|
55
55
|
</div>
|
|
56
56
|
</Variant>
|
|
57
|
-
<Variant title="
|
|
57
|
+
<Variant title="Combobox">
|
|
58
58
|
<div class="p-2">
|
|
59
59
|
<FormControl
|
|
60
|
-
type="
|
|
60
|
+
type="combobox"
|
|
61
61
|
:options="[
|
|
62
62
|
{ label: 'One', value: '1' },
|
|
63
63
|
{ label: 'Two', value: '2' },
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
<slot name="prefix" />
|
|
21
21
|
</template>
|
|
22
22
|
</Select>
|
|
23
|
+
<Combobox
|
|
24
|
+
v-else-if="type === 'combobox'"
|
|
25
|
+
:id="id"
|
|
26
|
+
v-bind="{ ...controlAttrs, variant }"
|
|
27
|
+
>
|
|
28
|
+
<template #prefix v-if="$slots.prefix">
|
|
29
|
+
<slot name="prefix" />
|
|
30
|
+
</template>
|
|
31
|
+
</Combobox>
|
|
23
32
|
<Autocomplete
|
|
24
33
|
v-else-if="type === 'autocomplete'"
|
|
25
34
|
v-bind="{ ...controlAttrs }"
|
|
@@ -66,6 +75,7 @@ import { Select } from '../Select'
|
|
|
66
75
|
import { Textarea } from '../Textarea'
|
|
67
76
|
import { Checkbox } from '../Checkbox'
|
|
68
77
|
import { Autocomplete } from '../Autocomplete'
|
|
78
|
+
import { Combobox } from '../Combobox'
|
|
69
79
|
import FormLabel from '../FormLabel.vue'
|
|
70
80
|
import type { FormControlProps } from './types'
|
|
71
81
|
|
|
@@ -3,7 +3,7 @@ import type { TextInputTypes } from '../types/TextInput'
|
|
|
3
3
|
export interface FormControlProps {
|
|
4
4
|
label?: string
|
|
5
5
|
description?: string
|
|
6
|
-
type?: TextInputTypes | 'textarea' | 'select' | 'checkbox' | 'autocomplete'
|
|
6
|
+
type?: TextInputTypes | 'textarea' | 'select' | 'checkbox' | 'autocomplete' | 'combobox'
|
|
7
7
|
size?: 'sm' | 'md'
|
|
8
8
|
variant?: 'subtle' | 'outline'
|
|
9
9
|
required?: boolean
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { reactive } from 'vue'
|
|
3
3
|
import Tabs from './Tabs.vue'
|
|
4
|
-
import
|
|
4
|
+
import LucideGithub from "~icons/lucide/github";
|
|
5
|
+
import LucideTwitter from "~icons/lucide/twitter";
|
|
6
|
+
import LucideLinkedin from "~icons/lucide/linkedin";
|
|
7
|
+
|
|
5
8
|
const state = reactive({
|
|
6
9
|
index: 0,
|
|
7
10
|
tabs_without_icon: [
|
|
@@ -21,24 +24,25 @@ const state = reactive({
|
|
|
21
24
|
'LinkedIn is an American business and employment-oriented online service that operates via websites and mobile apps.',
|
|
22
25
|
},
|
|
23
26
|
],
|
|
27
|
+
|
|
24
28
|
tabs_with_icon: [
|
|
25
29
|
{
|
|
26
30
|
label: 'Github',
|
|
27
31
|
content:
|
|
28
32
|
'Github is a code hosting platform for version control and collaboration. It lets you and others work together on projects from anywhere.',
|
|
29
|
-
icon:
|
|
33
|
+
icon: LucideGithub,
|
|
30
34
|
},
|
|
31
35
|
{
|
|
32
36
|
label: 'Twitter',
|
|
33
37
|
content:
|
|
34
38
|
'Twitter is an American microblogging and social networking service on which users post and interact with messages known as "tweets".',
|
|
35
|
-
icon:
|
|
39
|
+
icon: LucideTwitter,
|
|
36
40
|
},
|
|
37
41
|
{
|
|
38
42
|
label: 'Linkedin',
|
|
39
43
|
content:
|
|
40
44
|
'LinkedIn is an American business and employment-oriented online service that operates via websites and mobile apps.',
|
|
41
|
-
icon:
|
|
45
|
+
icon: LucideLinkedin,
|
|
42
46
|
},
|
|
43
47
|
],
|
|
44
48
|
})
|
|
@@ -48,7 +52,6 @@ const state = reactive({
|
|
|
48
52
|
<Story :layout="{ type: 'grid', width: '80%' }">
|
|
49
53
|
<Variant title="Without Icon">
|
|
50
54
|
<Tabs
|
|
51
|
-
as="div"
|
|
52
55
|
class="border rounded"
|
|
53
56
|
v-model="state.index"
|
|
54
57
|
:tabs="state.tabs_without_icon"
|
|
@@ -60,9 +63,9 @@ const state = reactive({
|
|
|
60
63
|
</template>
|
|
61
64
|
</Tabs>
|
|
62
65
|
</Variant>
|
|
66
|
+
|
|
63
67
|
<Variant title="With Icon">
|
|
64
68
|
<Tabs
|
|
65
|
-
as="div"
|
|
66
69
|
class="border rounded"
|
|
67
70
|
v-model="state.index"
|
|
68
71
|
:tabs="state.tabs_with_icon"
|
|
@@ -74,13 +77,13 @@ const state = reactive({
|
|
|
74
77
|
</template>
|
|
75
78
|
</Tabs>
|
|
76
79
|
</Variant>
|
|
80
|
+
|
|
77
81
|
<Variant title="Vertical Tabs">
|
|
78
82
|
<Tabs
|
|
79
|
-
as="div"
|
|
80
83
|
class="border rounded"
|
|
81
84
|
v-model="state.index"
|
|
82
85
|
:tabs="state.tabs_with_icon"
|
|
83
|
-
vertical
|
|
86
|
+
:vertical='true'
|
|
84
87
|
>
|
|
85
88
|
<template #tab-panel="{ tab }">
|
|
86
89
|
<div class="p-5">
|
|
@@ -1,57 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
TabsContent,
|
|
4
|
+
TabsIndicator,
|
|
5
|
+
TabsList,
|
|
6
|
+
TabsRoot,
|
|
7
|
+
TabsTrigger,
|
|
8
|
+
} from 'reka-ui'
|
|
9
|
+
|
|
10
|
+
import type { TabProps } from './types'
|
|
11
|
+
import { h } from 'vue'
|
|
12
|
+
|
|
13
|
+
const props = defineProps<TabProps>()
|
|
14
|
+
|
|
15
|
+
const indicatorXCss = `left-0 bottom-0 h-[1px] w-[--reka-tabs-indicator-size] transition-[width,transform]
|
|
16
|
+
translate-x-[--reka-tabs-indicator-position] translate-y-[1px]`
|
|
17
|
+
|
|
18
|
+
const indicatorYCss = `right-0 w-[1px] h-[--reka-tabs-indicator-size] transition-[height,transform]
|
|
19
|
+
translate-y-[--reka-tabs-indicator-position] translate-x-[1px]`
|
|
20
|
+
|
|
21
|
+
// Using a plain <button> element via `h('button')` to avoid picking up
|
|
22
|
+
// the globally registered Button component and its styles.
|
|
23
|
+
const Btn = h('button')
|
|
24
|
+
</script>
|
|
25
|
+
|
|
1
26
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class: ['flex flex-1 overflow-hidden', vertical ? '' : 'flex-col '],
|
|
8
|
-
}
|
|
9
|
-
: {}
|
|
10
|
-
"
|
|
11
|
-
:defaultIndex="tabIndex"
|
|
12
|
-
:selectedIndex="tabIndex"
|
|
13
|
-
@change="(idx) => (tabIndex = idx)"
|
|
27
|
+
<TabsRoot
|
|
28
|
+
:as="props.as"
|
|
29
|
+
class="data-[orientation=vertical]:flex"
|
|
30
|
+
:orientation="props.vertical ? 'vertical' : 'horizontal'"
|
|
31
|
+
:default-value="props.tabs[0].label"
|
|
14
32
|
>
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</template>
|
|
33
|
+
<TabsList
|
|
34
|
+
class="relative flex data-[orientation=vertical]:flex-col p-1 border-b data-[orientation=vertical]:border-r"
|
|
35
|
+
>
|
|
36
|
+
<TabsIndicator
|
|
37
|
+
class="absolute rounded-full duration-300"
|
|
38
|
+
:class="props.vertical ? indicatorYCss : indicatorXCss"
|
|
39
|
+
>
|
|
40
|
+
<div class="w-full h-full bg-surface-gray-7" />
|
|
41
|
+
</TabsIndicator>
|
|
25
42
|
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
provide(
|
|
50
|
-
'tab',
|
|
51
|
-
computed(() => ({
|
|
52
|
-
tabIndex,
|
|
53
|
-
tabs: props.tabs,
|
|
54
|
-
vertical: props.vertical,
|
|
55
|
-
})),
|
|
56
|
-
)
|
|
57
|
-
</script>
|
|
43
|
+
<TabsTrigger as="template" v-for="(tab, i) in props.tabs" :value="i">
|
|
44
|
+
<slot name="tab-item" v-bind="{ tab }">
|
|
45
|
+
<component
|
|
46
|
+
:is="tab.route ? 'router-link' : Btn"
|
|
47
|
+
:to="tab.route"
|
|
48
|
+
class="flex items-center gap-1.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:text-ink-gray-9 p-2.5 data-[state=active]:text-ink-gray-9"
|
|
49
|
+
:class="{ 'py-2.5': props.vertical, }"
|
|
50
|
+
>
|
|
51
|
+
<component v-if="tab.icon" :is="tab.icon" class="size-4">
|
|
52
|
+
</component>
|
|
53
|
+
|
|
54
|
+
{{ tab.label }}
|
|
55
|
+
</component>
|
|
56
|
+
</slot>
|
|
57
|
+
</TabsTrigger>
|
|
58
|
+
</TabsList>
|
|
59
|
+
|
|
60
|
+
<TabsContent v-for="(tab, i) in props.tabs" :value="i">
|
|
61
|
+
<slot name="tab-panel" v-bind="{ tab }"> </slot>
|
|
62
|
+
</TabsContent>
|
|
63
|
+
</TabsRoot>
|
|
64
|
+
</template>
|
|
@@ -257,7 +257,7 @@ function openLinkEditor(href: string, anchor: HTMLElement): Promise<string> {
|
|
|
257
257
|
content: container,
|
|
258
258
|
trigger: 'manual',
|
|
259
259
|
interactive: true,
|
|
260
|
-
appendTo: document.body,
|
|
260
|
+
appendTo: () => anchor.closest('[role="dialog"]') || document.body,
|
|
261
261
|
placement: 'top',
|
|
262
262
|
arrow: false,
|
|
263
263
|
theme: 'link-editor',
|
package/src/index.ts
CHANGED
|
@@ -33,8 +33,6 @@ export * from './components/Spinner'
|
|
|
33
33
|
export * from './components/Switch'
|
|
34
34
|
export * from './components/TabButtons'
|
|
35
35
|
export { default as Tabs } from './components/Tabs/Tabs.vue'
|
|
36
|
-
export { default as TabList } from './components/Tabs/TabList.vue'
|
|
37
|
-
export { default as TabPanel } from './components/Tabs/TabPanel.vue'
|
|
38
36
|
export * from './components/TextInput'
|
|
39
37
|
export * from './components/Textarea'
|
|
40
38
|
export * from './components/TextEditor'
|
package/vite/index.js
CHANGED
|
@@ -29,6 +29,22 @@ function frappeuiPlugin(
|
|
|
29
29
|
if (options.buildConfig) {
|
|
30
30
|
plugins.push(buildConfig(options.buildConfig))
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
const DepsIncludePlugin = {
|
|
34
|
+
name: 'optimize-deps-include',
|
|
35
|
+
config(config) {
|
|
36
|
+
if (!config.optimizeDeps) config.optimizeDeps = {}
|
|
37
|
+
|
|
38
|
+
const includedDeps = config.optimizeDeps?.include || []
|
|
39
|
+
const moduleName = 'highlight.js/lib/core'
|
|
40
|
+
|
|
41
|
+
if (includedDeps.includes(moduleName)) return
|
|
42
|
+
config.optimizeDeps.include = [moduleName, ...includedDeps]
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
plugins.push(DepsIncludePlugin)
|
|
47
|
+
|
|
32
48
|
return plugins
|
|
33
49
|
}
|
|
34
50
|
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<TabList
|
|
3
|
-
class="relative flex"
|
|
4
|
-
:class="
|
|
5
|
-
t.vertical
|
|
6
|
-
? 'flex-col border-r overflow-y-auto'
|
|
7
|
-
: 'gap-7.5 border-b overflow-x-auto items-center px-5'
|
|
8
|
-
"
|
|
9
|
-
>
|
|
10
|
-
<Tab
|
|
11
|
-
ref="tabRef"
|
|
12
|
-
as="template"
|
|
13
|
-
v-for="(tab, i) in t.tabs"
|
|
14
|
-
:key="i"
|
|
15
|
-
v-slot="{ selected }"
|
|
16
|
-
class="focus:outline-none focus:transition-none"
|
|
17
|
-
>
|
|
18
|
-
<slot v-bind="{ tab, selected }">
|
|
19
|
-
<button
|
|
20
|
-
class="flex items-center gap-1.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:text-ink-gray-9"
|
|
21
|
-
:class="[
|
|
22
|
-
selected ? 'text-ink-gray-9' : '',
|
|
23
|
-
t.vertical
|
|
24
|
-
? 'py-2.5 px-4 border-r border-transparent hover:border-outline-gray-3'
|
|
25
|
-
: 'py-3 border-b border-transparent hover:border-outline-gray-3',
|
|
26
|
-
]"
|
|
27
|
-
>
|
|
28
|
-
<component v-if="tab.icon" :is="tab.icon" class="size-4" />
|
|
29
|
-
{{ tab.label }}
|
|
30
|
-
</button>
|
|
31
|
-
</slot>
|
|
32
|
-
</Tab>
|
|
33
|
-
<div
|
|
34
|
-
ref="indicator"
|
|
35
|
-
class="tab-indicator absolute bg-surface-gray-7"
|
|
36
|
-
:class="[t.vertical ? 'right-0 w-px' : 'bottom-0 h-px', transitionClass]"
|
|
37
|
-
/>
|
|
38
|
-
</TabList>
|
|
39
|
-
</template>
|
|
40
|
-
<script setup>
|
|
41
|
-
import { TabList, Tab } from '@headlessui/vue'
|
|
42
|
-
import { ref, watch, computed, onMounted, nextTick, inject } from 'vue'
|
|
43
|
-
|
|
44
|
-
const t = inject('tab')
|
|
45
|
-
|
|
46
|
-
const tabRef = ref([])
|
|
47
|
-
const indicator = ref(null)
|
|
48
|
-
const tabsLength = computed(() => t.value.tabs.value?.length)
|
|
49
|
-
|
|
50
|
-
const transitionClass = ref('')
|
|
51
|
-
|
|
52
|
-
function moveIndicator(index) {
|
|
53
|
-
if (index >= tabsLength.value) {
|
|
54
|
-
index = tabsLength.value - 1
|
|
55
|
-
}
|
|
56
|
-
const selectedTab = tabRef.value[index].el
|
|
57
|
-
if (t.value.vertical.value) {
|
|
58
|
-
indicator.value.style.height = `${selectedTab.offsetHeight}px`
|
|
59
|
-
indicator.value.style.top = `${selectedTab.offsetTop}px`
|
|
60
|
-
} else {
|
|
61
|
-
indicator.value.style.width = `${selectedTab.offsetWidth}px`
|
|
62
|
-
indicator.value.style.left = `${selectedTab.offsetLeft}px`
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
watch(
|
|
67
|
-
() => t.value.tabIndex.value,
|
|
68
|
-
(index) => {
|
|
69
|
-
if (index >= tabsLength.value) {
|
|
70
|
-
t.value.tabIndex.value = tabsLength.value - 1
|
|
71
|
-
}
|
|
72
|
-
transitionClass.value = 'transition-all duration-300 ease-in-out'
|
|
73
|
-
nextTick(() => moveIndicator(index))
|
|
74
|
-
},
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
onMounted(() => {
|
|
78
|
-
nextTick(() => moveIndicator(t.value.tabIndex.value))
|
|
79
|
-
// Fix for indicator not moving on initial load
|
|
80
|
-
setTimeout(() => moveIndicator(t.value.tabIndex.value), 100)
|
|
81
|
-
})
|
|
82
|
-
</script>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<TabPanels class="flex flex-1 overflow-hidden">
|
|
3
|
-
<TabPanel
|
|
4
|
-
class="flex flex-1 flex-col overflow-y-auto focus:outline-none"
|
|
5
|
-
v-for="(tab, i) in t.tabs"
|
|
6
|
-
:key="i"
|
|
7
|
-
>
|
|
8
|
-
<slot v-bind="{ tab }" />
|
|
9
|
-
</TabPanel>
|
|
10
|
-
</TabPanels>
|
|
11
|
-
</template>
|
|
12
|
-
<script setup>
|
|
13
|
-
import { TabPanels, TabPanel } from '@headlessui/vue'
|
|
14
|
-
import { inject } from 'vue'
|
|
15
|
-
|
|
16
|
-
const t = inject('tab')
|
|
17
|
-
</script>
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
## Props
|
|
2
|
-
|
|
3
|
-
### tabs
|
|
4
|
-
|
|
5
|
-
It is an array of objects which contains the following attributes:
|
|
6
|
-
|
|
7
|
-
1. `label` is the name of the tab, it is required.
|
|
8
|
-
2. `icon` is the icon to be shown in the tab, it accept component and it is
|
|
9
|
-
optional.
|
|
10
|
-
3. You can add more attributes which can be used for custom rendering in the tab
|
|
11
|
-
header or content.
|
|
12
|
-
|
|
13
|
-
### v-model
|
|
14
|
-
|
|
15
|
-
It is used to set the active tab or change the active tab. It is required.
|
|
16
|
-
|
|
17
|
-
### vertical
|
|
18
|
-
|
|
19
|
-
It is used to show the tabs vertically. It is optional.
|
|
20
|
-
|
|
21
|
-
### as
|
|
22
|
-
|
|
23
|
-
You can set it to `div` to wrap tabs in a `div`. It can be any valid HTML tag.
|
|
24
|
-
This is useful to control the layout of the tabs. It is optional.
|
|
25
|
-
|
|
26
|
-
1. `as="div"` or any valid HTML tag
|
|
27
|
-
|
|
28
|
-
```html
|
|
29
|
-
<div>
|
|
30
|
-
<!-- container div -->
|
|
31
|
-
<div>
|
|
32
|
-
<div active>Tab 1</div>
|
|
33
|
-
<div>Tab 2</div>
|
|
34
|
-
<div>Tab 3</div>
|
|
35
|
-
</div>
|
|
36
|
-
<div>
|
|
37
|
-
<div active>Content 1</div>
|
|
38
|
-
<div>Content 2</div>
|
|
39
|
-
<div>Content 3</div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
2. `as` is not set
|
|
45
|
-
|
|
46
|
-
```html
|
|
47
|
-
<div>
|
|
48
|
-
<div active>Tab 1</div>
|
|
49
|
-
<div>Tab 2</div>
|
|
50
|
-
<div>Tab 3</div>
|
|
51
|
-
</div>
|
|
52
|
-
<div>
|
|
53
|
-
<div active>Content 1</div>
|
|
54
|
-
<div>Content 2</div>
|
|
55
|
-
<div>Content 3</div>
|
|
56
|
-
</div>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Slots
|
|
60
|
-
|
|
61
|
-
1. **tab-item:** You can use this slot to render custom tab items. It is
|
|
62
|
-
optional.
|
|
63
|
-
2. **tab-panel:** You can use this slot to render custom tab panels. It is
|
|
64
|
-
required. Example:
|
|
65
|
-
|
|
66
|
-
```vue
|
|
67
|
-
<Tabs v-model="tabIndex" :tabs="tabs">
|
|
68
|
-
<template #tab-item="{ tab, selected }">
|
|
69
|
-
<div :class="{ 'text-gray-900 font-semibold': selected }">
|
|
70
|
-
<span>{{ tab.label }}</span>
|
|
71
|
-
<span>{{ tab.icon }}</span>
|
|
72
|
-
</div>
|
|
73
|
-
</template>
|
|
74
|
-
<template #tab-panel="{ tab }">
|
|
75
|
-
<div>{{ tab.content }}</div>
|
|
76
|
-
</template>
|
|
77
|
-
</Tabs>
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Layout Customization
|
|
81
|
-
|
|
82
|
-
You can customize the layout of the tabs by using `<TabList />` and `<TabPanels />`
|
|
83
|
-
components.
|
|
84
|
-
|
|
85
|
-
```vue
|
|
86
|
-
<Tabs v-model="tabIndex" :tabs="tabs">
|
|
87
|
-
<TabList v-slot="{ tab, selected }">
|
|
88
|
-
<div :class="{ 'text-gray-900 font-semibold': selected }">
|
|
89
|
-
<span>{{ tab.label }}</span>
|
|
90
|
-
<span>{{ tab.icon }}</span>
|
|
91
|
-
</div>
|
|
92
|
-
</TabList>
|
|
93
|
-
<TabPanel v-slot="{ tab }">
|
|
94
|
-
<div>{{ tab.content }}</div>
|
|
95
|
-
</TabPanel>
|
|
96
|
-
</Tabs>
|
|
97
|
-
```
|