Nuxt UI v3-alpha 已发布!

试用一下
组件

选项卡

一组一次只显示一个的选项卡面板。

使用

将一个数组传递给 Tabs 组件的 items 属性。每个项目都可以具有以下属性

  • label - 项目的标签。
  • icon - 项目的图标。
  • slot - 用于使用插槽自定义项目的键。
  • content - 默认情况下在面板中显示的内容。
  • disabled - 确定项目是否被禁用。
这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Tab1',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Tab2',
  icon: 'i-heroicons-arrow-down-tray',
  disabled: true,
  content: 'And, this is the content for Tab2'
}, {
  label: 'Tab3',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]
</script>

<template>
  <UTabs :items="items" />
</template>

垂直

可以通过将 orientation 属性设置为 vertical 来更改选项卡的方向。

这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Tab1',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Tab2',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Tab3',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]
</script>

<template>
  <UTabs :items="items" orientation="vertical" :ui="{ wrapper: 'flex items-center gap-4', list: { width: 'w-48' } }" />
</template>

默认索引

可以通过设置 default-index 属性来设置选项卡的默认索引。

最后,这是选项卡 3 的内容
<script setup lang="ts">
const items = [{
  label: 'Tab1',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Tab2',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Tab3',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]
</script>

<template>
  <UTabs :items="items" :default-index="2" />
</template>
如果您使用 v-model 来控制所选索引,则此设置将无效。

监听更改

可以使用 @change 事件监听更改。该事件将发出所选项目的索引。

这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Tab1',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Tab2',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Tab3',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]

function onChange(index) {
  const item = items[index]

  alert(`${item.label} was clicked!`)
}
</script>

<template>
  <UTabs :items="items" @change="onChange" />
</template>

您可以使用 content 属性并将其设置为 false,以便在不需要 HTML 内容时避免渲染。

控制所选索引

使用 v-model 来控制所选索引。

这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Tab1',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Tab2',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Tab3',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]

const route = useRoute()
const router = useRouter()

const selected = computed({
  get() {
    const index = items.findIndex(item => item.label === route.query.tab)
    if (index === -1) {
      return 0
    }

    return index
  },
  set(value) {
    // Hash is specified here to prevent the page from scrolling to the top
    router.replace({ query: { tab: items[value].label }, hash: '#control-the-selected-index' })
  }
})
</script>

<template>
  <UTabs v-model="selected" :items="items" />
</template>
在本例中,我们正在将选项卡绑定到路由查询。刷新页面以查看所选选项卡的更改。

插槽

可以使用插槽来自定义手风琴的按钮和项目内容。

默认

使用 #default 插槽来自定义触发按钮的内容。您将在插槽范围内访问到 itemindexselecteddisabled

这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Getting Started',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Installation',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Theming',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]
</script>

<template>
  <UTabs :items="items" class="w-full">
    <template #default="{ item, index, selected }">
      <span class="truncate" :class="[selected && 'text-primary-500 dark:text-primary-400']">{{ index + 1 }}. {{ item.label }}</span>
    </template>
  </UTabs>
</template>

图标

使用 #icon 插槽来自定义触发按钮的图标。您将在插槽范围内访问到 itemindexselecteddisabled

这是选项卡 1 显示的内容
<script setup lang="ts">
const items = [{
  label: 'Getting Started',
  icon: 'i-heroicons-information-circle',
  content: 'This is the content shown for Tab1'
}, {
  label: 'Installation',
  icon: 'i-heroicons-arrow-down-tray',
  content: 'And, this is the content for Tab2'
}, {
  label: 'Theming',
  icon: 'i-heroicons-eye-dropper',
  content: 'Finally, this is the content for Tab3'
}]
</script>

<template>
  <UTabs :items="items" class="w-full">
    <template #icon="{ item, selected }">
      <UIcon :name="item.icon" class="w-4 h-4 flex-shrink-0 me-2" :class="[selected && 'text-primary-500 dark:text-primary-400']" />
    </template>
  </UTabs>
</template>

项目

使用 #item 插槽来自定义项目内容。您将在插槽范围内访问到 itemindexselected 属性。

帐户

在这里更改您的帐户。完成后,点击保存。

<script setup lang="ts">
const items = [{
  key: 'account',
  label: 'Account',
  description: 'Make changes to your account here. Click save when you\'re done.'
}, {
  key: 'password',
  label: 'Password',
  description: 'Change your password here. After saving, you\'ll be logged out.'
}]

const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
const passwordForm = reactive({ currentPassword: '', newPassword: '' })

function onSubmit(form) {
  console.log('Submitted form:', form)
}
</script>

<template>
  <UTabs :items="items" class="w-full">
    <template #item="{ item }">
      <UCard @submit.prevent="() => onSubmit(item.key === 'account' ? accountForm : passwordForm)">
        <template #header>
          <p class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
            {{ item.label }}
          </p>
          <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
            {{ item.description }}
          </p>
        </template>

        <div v-if="item.key === 'account'" class="space-y-3">
          <UFormGroup label="Name" name="name">
            <UInput v-model="accountForm.name" />
          </UFormGroup>
          <UFormGroup label="Username" name="username">
            <UInput v-model="accountForm.username" />
          </UFormGroup>
        </div>
        <div v-else-if="item.key === 'password'" class="space-y-3">
          <UFormGroup label="Current Password" name="current" required>
            <UInput v-model="passwordForm.currentPassword" type="password" required />
          </UFormGroup>
          <UFormGroup label="New Password" name="new" required>
            <UInput v-model="passwordForm.newPassword" type="password" required />
          </UFormGroup>
        </div>

        <template #footer>
          <UButton type="submit" color="black">
            Save {{ item.key === 'account' ? 'account' : 'password' }}
          </UButton>
        </template>
      </UCard>
    </template>
  </UTabs>
</template>

您还可以传递一个 slot 属性来自定义特定项目。

帐户

在这里更改您的帐户。完成后,点击保存。

<script setup lang="ts">
const items = [{
  slot: 'account',
  label: 'Account'
}, {
  slot: 'password',
  label: 'Password'
}]

const accountForm = reactive({ name: 'Benjamin', username: 'benjamincanac' })
const passwordForm = reactive({ currentPassword: '', newPassword: '' })

function onSubmitAccount() {
  console.log('Submitted form:', accountForm)
}

function onSubmitPassword() {
  console.log('Submitted form:', passwordForm)
}
</script>

<template>
  <UTabs :items="items" class="w-full">
    <template #account="{ item }">
      <UCard @submit.prevent="onSubmitAccount">
        <template #header>
          <p class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
            {{ item.label }}
          </p>
          <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
            Make changes to your account here. Click save when you're done.
          </p>
        </template>

        <UFormGroup label="Name" name="name" class="mb-3">
          <UInput v-model="accountForm.name" />
        </UFormGroup>
        <UFormGroup label="Username" name="username">
          <UInput v-model="accountForm.username" />
        </UFormGroup>

        <template #footer>
          <UButton type="submit" color="black">
            Save account
          </UButton>
        </template>
      </UCard>
    </template>

    <template #password="{ item }">
      <UCard @submit.prevent="onSubmitPassword">
        <template #header>
          <h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
            {{ item.label }}
          </h3>
          <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
            Change your password here. After saving, you'll be logged out.
          </p>
        </template>

        <UFormGroup label="Current Password" name="current" required class="mb-3">
          <UInput v-model="passwordForm.currentPassword" type="password" required />
        </UFormGroup>
        <UFormGroup label="New Password" name="new" required>
          <UInput v-model="passwordForm.newPassword" type="password" required />
        </UFormGroup>

        <template #footer>
          <UButton type="submit" color="black">
            Save password
          </UButton>
        </template>
      </UCard>
    </template>
  </UTabs>
</template>

属性

ui
{ wrapper?: string; container?: string; base?: string; list?: DeepPartial<{ base: string; background: string; rounded: string; shadow: string; padding: string; height: string; width: string; marker: { wrapper: string; base: string; background: string; rounded: string; shadow: string; }; tab: { ...; }; }, any>; } & {...
{}
orientation
"horizontal" | "vertical"
"horizontal"
modelValue
number
undefined
defaultIndex
number
0
items
TabItem[]
[]
unmount
boolean
false
content
boolean
true

配置

{
  wrapper: 'relative space-y-2',
  container: 'relative w-full',
  base: 'focus:outline-none',
  list: {
    base: 'relative',
    background: 'bg-gray-100 dark:bg-gray-800',
    rounded: 'rounded-lg',
    shadow: '',
    padding: 'p-1',
    height: 'h-10',
    width: 'w-full',
    marker: {
      wrapper: 'absolute top-[4px] left-[4px] duration-200 ease-out focus:outline-none',
      base: 'w-full h-full',
      background: 'bg-white dark:bg-gray-900',
      rounded: 'rounded-md',
      shadow: 'shadow-sm'
    },
    tab: {
      base: 'relative inline-flex items-center justify-center flex-shrink-0 w-full ui-focus-visible:outline-0 ui-focus-visible:ring-2 ui-focus-visible:ring-primary-500 dark:ui-focus-visible:ring-primary-400 ui-not-focus-visible:outline-none focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors duration-200 ease-out',
      background: '',
      active: 'text-gray-900 dark:text-white',
      inactive: 'text-gray-500 dark:text-gray-400',
      height: 'h-8',
      padding: 'px-3',
      size: 'text-sm',
      font: 'font-medium',
      rounded: 'rounded-md',
      shadow: '',
      icon: 'w-4 h-4 flex-shrink-0 me-2'
    }
  }
}