Tabs.vue
1.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<script setup>
/**
* Tabs —— 受控分段切换(v-model 绑当前 tab 的 key)。每个 tab 的内容走同名插槽,
* 只渲染当前那一个(不做 keep-alive,切走即卸载)。
*
* @typedef {{ key: string, label: string }} TabItem
*/
defineProps({
/** 当前选中的 tab key。 */
modelValue: { type: String, required: true },
/** @type {import('vue').PropType<TabItem[]>} tab 全集,顺序即展示顺序。 */
tabs: { type: Array, required: true },
})
defineEmits(['update:modelValue'])
</script>
<template>
<div class="drama-tabs">
<div
class="drama-tabs__list"
role="tablist"
>
<button
v-for="tab in tabs"
:key="tab.key"
class="drama-tabs__tab"
:class="{ 'is-active': tab.key === modelValue }"
type="button"
role="tab"
:aria-selected="tab.key === modelValue ? 'true' : 'false'"
@click="$emit('update:modelValue', tab.key)"
>
{{ tab.label }}
</button>
</div>
<div
class="drama-tabs__panel"
role="tabpanel"
>
<slot :name="modelValue" />
</div>
</div>
</template>
<style scoped>
.drama-tabs__list {
display: flex;
gap: var(--drama-space-xs);
border-bottom: 1px solid var(--drama-color-border);
}
.drama-tabs__tab {
padding: var(--drama-space-sm) var(--drama-space-lg);
border: none;
border-bottom: 2px solid transparent;
background: transparent;
color: var(--drama-color-text-muted);
font-family: var(--drama-font-family-body);
font-size: var(--drama-font-size-sm);
font-weight: var(--drama-font-weight-medium);
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
}
.drama-tabs__tab:hover {
color: var(--drama-color-text);
}
.drama-tabs__tab.is-active {
color: var(--drama-color-primary);
border-bottom-color: var(--drama-color-primary);
}
.drama-tabs__panel {
padding-top: var(--drama-space-lg);
}
</style>