Admin 元件

Tabs

參考API
分頁切換。

安裝

Terminal
pnpm dlx shadcn-vue add @admin/tabs
import {  AdminTabs,  AdminTabsContent,  AdminTabsList,  AdminTabsTrigger,} from '@/components/admin/tabs'

用法

<script setup lang="ts">import {  AdminTabs,  AdminTabsContent,  AdminTabsList,  AdminTabsTrigger} from '@/components/admin/tabs'</script>​<template>  <admin-tabs class="w-80" default-value="a">    <admin-tabs-list>      <admin-tabs-trigger value="a">Account</admin-tabs-trigger>      <admin-tabs-trigger value="b">Password</admin-tabs-trigger>    </admin-tabs-list>    <admin-tabs-content class="pt-3" value="a">Account panel</admin-tabs-content>    <admin-tabs-content class="pt-3" value="b">Password panel</admin-tabs-content>  </admin-tabs></template>

場景

受控模式

<script setup lang="ts">import { ref } from 'vue'import { AdminButton } from '@/components/admin/button'import {  AdminTabs,  AdminTabsContent,  AdminTabsList,  AdminTabsTrigger} from '@/components/admin/tabs'​const active = ref<string | number>('account')</script>​<template>  <div class="flex flex-col gap-3">    <div class="flex items-center gap-2">      <span class="typo-content-medium">目前分頁:{{ active }}</span>      <admin-button size="small" @click="active = active === 'account' ? 'password' : 'account'">        外部切換      </admin-button>    </div>    <admin-tabs v-model="active" class="w-80">      <admin-tabs-list>        <admin-tabs-trigger value="account">Account</admin-tabs-trigger>        <admin-tabs-trigger value="password">Password</admin-tabs-trigger>      </admin-tabs-list>      <admin-tabs-content class="pt-3" value="account">Account panel</admin-tabs-content>      <admin-tabs-content class="pt-3" value="password">Password panel</admin-tabs-content>    </admin-tabs>  </div></template>

Type-safe 封裝

AdminTabs 已是 generic(T extends StringOrNumber),以較嚴的 ref 綁定 v-model 即可收斂為 tab value 的 literal union。若要讓 value 與清單對齊、並讓每個 tab 的內容仍保有完整彈性,可自行封裝:傳入 items(建議加 as const),由封裝元件以 items 為單一事實來源渲染各 trigger 與 content shell,content 由使用端以 #panel-<value> 具名 slot 填入。

如此 items 即聯集來源,各 trigger 的 value 結構上不可能越界;型別安全落在 v-model 與 defaultValue 必須對齊 items 的 value union(允許 undefined 表未選)。封裝再以 defineSlots 將 content slot 收斂為 panel-<value> 聯集,填入不在清單內的 slot 名亦會編譯期報錯。

此食譜刻意精簡,未透出 trigger 的 badge/badgeValue;需要時可自行 fork 擴充。封裝元件僅為文件示範,不屬 registry 安裝產物。

TypedAdminTabs.vue
<script  setup  lang="ts"  generic="    TItems extends ReadonlyArray<{      value: string | number      label: string      disabled?: boolean    }>  ">import type { AdminTabsEmits, AdminTabsProps } from '@/components/admin/tabs'import { reactiveOmit } from '@vueuse/core'import { useForwardPropsEmits } from 'reka-ui'import {  AdminTabs,  AdminTabsContent,  AdminTabsList,  AdminTabsTrigger} from '@/components/admin/tabs'​type TItem = TItems[number]// v-model / defaultValue 型別跟著 items 走:允許 undefined 表初始未選type TValue = TItem['value']​defineOptions({  name: 'typed-admin-tabs'})​const props = defineProps<  AdminTabsProps<TValue> & {    items: TItems  }>()​const emits = defineEmits<  Omit<AdminTabsEmits<TValue>, 'update:modelValue'> & {    'update:modelValue': [value: TValue]  }>()​defineSlots<Partial<Record<`panel-${TValue}`, (props: Record<string, never>) => unknown>>>()​const delegatedProps = reactiveOmit(props, 'items')const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>​<template>  <admin-tabs v-bind="forwarded">    <admin-tabs-list>      <admin-tabs-trigger        v-for="item in props.items"        :key="item.value"        :disabled="item.disabled"        :value="item.value"      >        {{ item.label }}      </admin-tabs-trigger>    </admin-tabs-list>    <admin-tabs-content v-for="item in props.items" :key="item.value" :value="item.value">      <slot :name="`panel-${item.value}` as `panel-${TValue}`"></slot>    </admin-tabs-content>  </admin-tabs></template>
需開啟 strictVModel

型別檢查需開啟 strictVModel(若已開啟 strictTemplates 則已包含)。設定見 Type-safe。下列同時含合法綁定與刻意錯誤綁定;錯誤處以 @vue-expect-error 標示。

<script setup lang="ts">import { ref } from 'vue'import TypedAdminTabs from './typed-admin-tabs-demo.vue'​const items = [  { value: 'overview', label: '總覽' },  { value: 'settings', label: '設定' }] as const​const active = ref<(typeof items)[number]['value']>('overview')const wrong = ref('unknown')const wrongDefault = 'unknown'</script>​<template>  <div class="flex w-full max-w-md flex-col gap-6">    <typed-admin-tabs v-model="active" :items="items">      <template #panel-overview>總覽內容</template>      <template #panel-settings>設定內容</template>    </typed-admin-tabs>​    <!-- @vue-expect-error value 不在 items 的 value union 內 -->    <typed-admin-tabs v-model="wrong" :items="items">      <template #panel-overview>總覽內容</template>      <template #panel-settings>設定內容</template>    </typed-admin-tabs>​    <!-- @vue-expect-error defaultValue 不在 items 的 value union 內 -->    <typed-admin-tabs :default-value="wrongDefault" :items="items">      <template #panel-overview>總覽內容</template>      <template #panel-settings>設定內容</template>    </typed-admin-tabs>​    <typed-admin-tabs v-model="active" :items="items">      <template #panel-overview>總覽內容</template>      <template #panel-settings>設定內容</template>      <!-- @vue-expect-error slot 名不在 items 的 panel-<value> 聯集內 -->      <template #panel-typo>不存在的 slot</template>    </typed-admin-tabs>  </div></template>

API 參考

組成

<template>  <admin-tabs default-value="overview">    <admin-tabs-list>      <admin-tabs-trigger value="overview">概覽</admin-tabs-trigger>      <admin-tabs-trigger value="details">明細</admin-tabs-trigger>    </admin-tabs-list>    <admin-tabs-content value="overview">概覽內容</admin-tabs-content>    <admin-tabs-content value="details">明細內容</admin-tabs-content>  </admin-tabs></template>

admin-tabs

Props

名稱型別預設值說明
classClassValue—根元素之自訂 class
defaultValueTModelValue | undefined—The value of the tab that should be active when initially rendered. Use when you do not need to control the state of the tabs
orientationDataOrientation | undefinedhorizontalThe orientation the tabs are laid out. Mainly so arrow navigation is done accordingly (left & right vs. up & down)
dirDirection | undefined—The reading direction of the combobox when applicable. If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode.
activationMode"automatic" | "manual" | undefinedautomaticWhether a tab is activated automatically (on focus) or manually (on click).
modelValueTModelValue | undefined—The controlled value of the tab to activate. Can be bind as `v-model`.
unmountOnHideboolean | undefined`true`When `true`, the element will be unmounted on closed state.
asChildboolean | undefined—Change the default rendered element for the one passed as a child, merging their props and behavior. Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.
asglobalThis.Component | AsTag | undefined"div"The element or component this component should render as. Can be overwritten by `asChild`.
size"xs" | "sm" | undefined"xs"tabs trigger 尺寸(透過 context 傳遞給所有 trigger)

Events

名稱Payload說明
update:modelValueTModelValue—

Slots

名稱Bindings說明
default{}—

admin-tabs-list

Props

名稱型別預設值說明
loopboolean | undefined—When `true`, keyboard navigation will loop from last tab to first, and vice versa.
asChildboolean | undefined—Change the default rendered element for the one passed as a child, merging their props and behavior. Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.
asglobalThis.Component | AsTag | undefined"div"The element or component this component should render as. Can be overwritten by `asChild`.
classClassValue—根元素之自訂 class

Slots

名稱Bindings說明
default{}—

admin-tabs-trigger

Props

名稱型別預設值說明
value*StringOrNumber—A unique value that associates the trigger with a content.
disabledboolean | undefined—When `true`, prevents the user from interacting with the tab.
asChildboolean | undefined—Change the default rendered element for the one passed as a child, merging their props and behavior. Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.
asglobalThis.Component | AsTag | undefined"div"The element or component this component should render as. Can be overwritten by `asChild`.
classClassValue—根元素之自訂 class
badgeboolean | undefinedfalse設為 `true` 時於 trigger 右側顯示 badge
badgeValuenumber | undefined—badge 顯示的數字(搭配 `badge: true` 使用)

* 代表必填

Slots

名稱Bindings說明
default{}—

admin-tabs-content

Props

名稱型別預設值說明
value*StringOrNumber—A unique value that associates the content with a trigger.
forceMountboolean | undefined—Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.
asChildboolean | undefined—Change the default rendered element for the one passed as a child, merging their props and behavior. Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details.
asglobalThis.Component | AsTag | undefined"div"The element or component this component should render as. Can be overwritten by `asChild`.
classClassValue—根元素之自訂 class

* 代表必填

Slots

名稱Bindings說明
default{}—

更新紀錄

  • fix: 補上 Vue 核心 API 的顯式 import3c2c030
  • feat: 補回 root 泛型使 v-model 收斂,新增 type-safe wrapper 食譜614dc62
  • fix: 修正 hover 壓縮 active 底線與 badgeValue 為 0 仍顯示54a3c76
  • chore: 啟用 vue/no-useless-v-bind 與 vue/prefer-separate-static-class16c34a8