用法
项
使用 items
prop,其值为一个对象数组,具有以下属性:
icon?: string
label?: string
trailingIcon?: string
defaultExpanded?: boolean
disabled?: boolean
value?: string
slot?: string
children?: TreeItem[]
onToggle?(e: Event): void
onSelect?(e?: Event): void
value
prop 作为标识符,如果未提供 value
,则回退使用 label
。组件正常工作需要提供其中一个。<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template>
多选
使用 multiple
prop 允许多项选择。
<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree multiple :items="items" />
</template>
颜色
使用 color
prop 改变 Tree 的颜色。
<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree color="neutral" :items="items" />
</template>
尺寸
使用 size
prop 改变 Tree 的尺寸。
<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree size="xl" :items="items" />
</template>
尾随图标
使用 trailing-icon
prop 自定义父节点的尾随 Icon。默认为 i-lucide-chevron-down
。
<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
trailingIcon: 'i-lucide-chevron-down',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree trailing-icon="i-lucide-arrow-down" :items="items" />
</template>
展开图标
使用 expanded-icon
和 collapsed-icon
props 自定义父节点展开或折叠时的图标。默认为 i-lucide-folder-open
和 i-lucide-folder
。
<script setup lang="ts">
const items = ref([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" :items="items" />
</template>
禁用
使用 disabled
prop 防止用户与 Tree 进行任何交互。
item.disabled
禁用单个项。<script setup lang="ts">
const items = ref([
{
label: 'app',
icon: 'i-lucide-folder',
defaultExpanded: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree disabled :items="items" />
</template>
示例
控制选定的项
您可以使用 default-value
prop 或 v-model
指令来控制选定的项。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const value = ref()
</script>
<template>
<UTree v-model="value" :items="items" />
</template>
如果您想阻止某个项被选中,可以使用 item.onSelect()
属性
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
onSelect: (e: Event) => {
e.preventDefault()
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
</script>
<template>
<UTree :items="items" />
</template>
控制展开的项
您可以使用 default-expanded
prop 或 v-model
指令来控制展开的项。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
value: 'app',
children: [
{
label: 'composables/',
value: 'composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
value: 'components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const expanded = ref(['app', 'composables'])
</script>
<template>
<UTree v-model:expanded="expanded" :items="items" />
</template>
如果您想阻止某个项被展开,可以使用 item.onToggle()
属性
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
onToggle: (e: Event) => {
e.preventDefault()
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
</script>
<template>
<UTree :items="items" />
</template>
使用自定义插槽
使用 item.slot
属性自定义特定项。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = [
{
label: 'app/',
slot: 'app' as const,
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
] satisfies TreeItem[]
</script>
<template>
<UTree :items="items">
<template #app="{ item }">
<p class="italic font-bold">
{{ item.label }}
</p>
</template>
</UTree>
</template>
API
Props
Prop | 默认值 | 类型 |
---|---|---|
as |
|
此组件应渲染为的元素或组件。 |
color |
|
|
size |
|
|
valueKey |
|
用于获取项值的键。 |
labelKey |
|
用于获取项标签的键。 |
trailingIcon |
|
显示在父节点右侧的图标。 |
expandedIcon |
|
父节点展开时显示的图标。 |
collapsedIcon |
|
父节点折叠时显示的图标。 |
items |
| |
modelValue |
Tree 的受控值。可以绑定为 | |
defaultValue |
Tree 初次渲染时的值。当您不需要控制 Tree 的状态时使用。 | |
multiple |
是否可以选中多个选项。 | |
defaultExpanded |
展开的 tree 初次渲染时的值。当您不需要控制展开的 tree 的状态时使用。 | |
disabled |
当 | |
expanded |
展开项的受控值。可以绑定 | |
selectionBehavior |
集合中多选的行为方式。 | |
propagateSelect |
当 | |
ui |
|
插槽
插槽 | 类型 |
---|---|
item |
|
item-leading |
|
item-label |
|
item-trailing |
|
Emits
事件 | 类型 |
---|---|
update:modelValue |
|
update:expanded |
|
主题
export default defineAppConfig({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: '',
listWithChildren: 'ms-4.5 border-s border-default',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2',
linkLeadingIcon: 'shrink-0',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180'
},
variants: {
color: {
primary: {
link: 'focus-visible:before:ring-primary'
},
secondary: {
link: 'focus-visible:before:ring-secondary'
},
success: {
link: 'focus-visible:before:ring-success'
},
info: {
link: 'focus-visible:before:ring-info'
},
warning: {
link: 'focus-visible:before:ring-warning'
},
error: {
link: 'focus-visible:before:ring-error'
},
neutral: {
link: 'focus-visible:before:ring-inverted'
}
},
size: {
xs: {
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-elevated'
},
false: {
link: [
'hover:not-disabled:text-highlighted hover:not-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
]
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'text-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'text-highlighted'
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: '',
listWithChildren: 'ms-4.5 border-s border-default',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2',
linkLeadingIcon: 'shrink-0',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180'
},
variants: {
color: {
primary: {
link: 'focus-visible:before:ring-primary'
},
secondary: {
link: 'focus-visible:before:ring-secondary'
},
success: {
link: 'focus-visible:before:ring-success'
},
info: {
link: 'focus-visible:before:ring-info'
},
warning: {
link: 'focus-visible:before:ring-warning'
},
error: {
link: 'focus-visible:before:ring-error'
},
neutral: {
link: 'focus-visible:before:ring-inverted'
}
},
size: {
xs: {
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-elevated'
},
false: {
link: [
'hover:not-disabled:text-highlighted hover:not-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
]
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'text-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'text-highlighted'
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})
]
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
plugins: [
vue(),
uiPro({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: '',
listWithChildren: 'ms-4.5 border-s border-default',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'relative group w-full flex items-center text-sm before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:ring-inset focus-visible:before:ring-2',
linkLeadingIcon: 'shrink-0',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 group-data-expanded:rotate-180'
},
variants: {
color: {
primary: {
link: 'focus-visible:before:ring-primary'
},
secondary: {
link: 'focus-visible:before:ring-secondary'
},
success: {
link: 'focus-visible:before:ring-success'
},
info: {
link: 'focus-visible:before:ring-info'
},
warning: {
link: 'focus-visible:before:ring-warning'
},
error: {
link: 'focus-visible:before:ring-error'
},
neutral: {
link: 'focus-visible:before:ring-inverted'
}
},
size: {
xs: {
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-elevated'
},
false: {
link: [
'hover:not-disabled:text-highlighted hover:not-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
]
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'text-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'text-highlighted'
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})
]
})