Tabs.vue 1.91 KB
<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>