fix(components): improve generic types (#3331)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Sandro Circi
2025-03-24 21:38:13 +01:00
committed by GitHub
parent 370054b20c
commit b9983549a4
106 changed files with 1203 additions and 535 deletions

View File

@@ -18,6 +18,7 @@ const items = [{
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
label: 'Components',
slot: 'test' as const,
icon: 'i-lucide-layers-3',
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.'
}, {
@@ -37,6 +38,11 @@ const items = [{
</p>
</template>
<template #custom="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}
</p>
</template>
<template #custom-body="{ item }">
<p class="text-(--ui-text-muted)">
Custom: {{ item.content }}

View File

@@ -150,10 +150,6 @@ defineShortcuts(extractShortcuts(items.value))
<UDropdownMenu :items="itemsWithColor" :size="size" arrow :content="{ side: 'bottom', align: 'start' }" :ui="{ content: 'w-48' }">
<UButton label="Color" color="neutral" variant="outline" icon="i-lucide-menu" />
<template #custom-trailing>
<UIcon name="i-lucide-badge-check" class="shrink-0 size-5 text-(--ui-primary)" />
</template>
</UDropdownMenu>
</div>
</div>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { InputMenuItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import { refDebounced } from '@vueuse/core'
import type { User } from '~/types'
@@ -10,7 +12,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -28,7 +30,7 @@ const statuses = [{
}, {
label: 'Canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies InputMenuItem[]
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -126,7 +128,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue?.avatar" :size="ui.itemLeadingAvatarSize()" v-bind="modelValue.avatar" />
<UAvatar v-if="modelValue?.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="modelValue.avatar" />
</template>
</UInputMenu>
</div>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import type { SelectMenuItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import { refDebounced } from '@vueuse/core'
import theme from '#build/ui/select-menu'
@@ -10,7 +12,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]] satisfies SelectMenuItem[][]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -33,7 +35,7 @@ const statuses = [{
label: 'Canceled',
value: 'canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies SelectMenuItem[]
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
@@ -41,7 +43,7 @@ const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTermDebounced },
transform: (data: User[]) => {
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
return data?.map(user => ({ id: user.id, label: user.name, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } }))
},
lazy: true
})
@@ -122,7 +124,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
v-for="size in sizes"
:key="size"
v-model:search-term="searchTerm"
:items="users || []"
:items="users"
:loading="status === 'pending'"
ignore-filter
icon="i-lucide-user"
@@ -132,7 +134,7 @@ const { data: users, status } = await useFetch('https://jsonplaceholder.typicode
@update:open="searchTerm = ''"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue?.avatar" :size="ui.itemLeadingAvatarSize()" v-bind="modelValue.avatar" />
<UAvatar v-if="modelValue?.avatar" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="modelValue.avatar" />
</template>
</USelectMenu>
</div>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import type { SelectItem, AvatarProps } from '@nuxt/ui'
import { upperFirst } from 'scule'
import theme from '#build/ui/select'
import type { User } from '~/types'
@@ -9,7 +10,7 @@ const variants = Object.keys(theme.variants.variant) as Array<keyof typeof theme
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
const items = [[{ label: 'Fruits', type: 'label' }, ...fruits], [{ label: 'Vegetables', type: 'label' }, ...vegetables]]
const items = [[{ label: 'Fruits', type: 'label' as const }, ...fruits], [{ label: 'Vegetables', type: 'label' as const }, ...vegetables]]
const selectedItems = ref([fruits[0]!, vegetables[0]!])
const statuses = [{
@@ -32,7 +33,7 @@ const statuses = [{
label: 'Canceled',
value: 'canceled',
icon: 'i-lucide-circle-x'
}]
}] satisfies SelectItem[]
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: User[]) => {
@@ -114,9 +115,10 @@ function getUserAvatar(value: string) {
trailing-icon="i-lucide-chevrons-up-down"
:size="size"
class="w-48"
value-key="value"
>
<template #leading="{ modelValue, ui }">
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue as string)" :class="ui.leadingIcon()" />
<UIcon v-if="modelValue" :name="getStatusIcon(modelValue)" :class="ui.leadingIcon()" />
</template>
</USelect>
</div>
@@ -130,9 +132,10 @@ function getUserAvatar(value: string) {
placeholder="Search users..."
:size="size"
class="w-48"
value-key="value"
>
<template #leading="{ modelValue, ui }">
<UAvatar v-if="modelValue" :size="ui.itemLeadingAvatarSize()" v-bind="getUserAvatar(modelValue as string)" />
<UAvatar v-if="modelValue" :size="(ui.itemLeadingAvatarSize() as AvatarProps['size'])" v-bind="getUserAvatar(modelValue)" />
</template>
</USelect>
</div>

View File

@@ -11,22 +11,22 @@ const size = ref('md' as const)
const items = [
{
slot: 'address',
slot: 'address' as const,
title: 'Address',
description: 'Add your address here',
icon: 'i-lucide-house'
}, {
slot: 'shipping',
slot: 'shipping' as const,
title: 'Shipping',
description: 'Set your preferred shipping method',
icon: 'i-lucide-truck'
}, {
slot: 'payment',
slot: 'payment' as const,
title: 'Payment',
description: 'Select your payment method',
icon: 'i-lucide-credit-card'
}, {
slot: 'checkout',
slot: 'checkout' as const,
title: 'Checkout',
description: 'Confirm your order'
}
@@ -50,27 +50,27 @@ const stepper = useTemplateRef('stepper')
:orientation="orientation"
:size="size"
>
<template #address>
<template #address="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Address
{{ item.title }}
</Placeholder>
</template>
<template #shipping>
<template #shipping="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Shipping
{{ item.title }}
</Placeholder>
</template>
<template #payment>
<template #payment="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Payment
{{ item.title }}
</Placeholder>
</template>
<template #checkout>
<template #checkout="{ item }">
<Placeholder class="size-full min-h-60 min-w-60">
Checkout
{{ item.title }}
</Placeholder>
</template>
</UStepper>

View File

@@ -41,8 +41,8 @@ const itemsWithMappedId = [
{ id: 'id3', title: 'obiwan kenobi' }
]
const modelValue = ref<TreeItem>()
const modelValues = ref<TreeItem[]>([])
const modelValue = ref<string>()
const modelValues = ref<string[]>([])
</script>
<template>
@@ -64,22 +64,14 @@ const modelValues = ref<TreeItem[]>([])
<!-- Typescript tests -->
<template v-if="false">
<!-- @vue-expect-error - multiple props should type modelValue to array. -->
<UTree :model-value="modelValue" :items="items" multiple />
<!-- @vue-expect-error - multiple props should type defaultValue to array. -->
<UTree :default-value="modelValue" :items="items" multiple />
<!-- @vue-expect-error - multiple props should type @update:modelValue to array. -->
<UTree :items="items" multiple @update:model-value="(payload: TreeItem) => payload" />
<!-- @vue-expect-error - default should type modelValue to single item. -->
<UTree :model-value="modelValues" :items="items" />
<!-- @vue-expect-error - default should type defaultValue to single item. -->
<UTree :default-value="modelValues" :items="items" />
<!-- @vue-expect-error - default should type @update:modelValue to single item. -->
<UTree :items="items" @update:model-value="(payload: TreeItem[]) => payload" />
<UTree :model-value="modelValues" :items="items" multiple />
<UTree :default-value="modelValues" :items="items" multiple />
<UTree :items="items" multiple @update:model-value="(payload) => payload" />
<UTree :model-value="modelValue" :items="items" />
<UTree :default-value="modelValue" :items="items" />
<UTree :items="items" @update:model-value="(payload) => payload" />
<!-- @vue-expect-error - value key should type v-model. -->
<UTree v-model="modelValue" :items="itemsWithMappedId" value-key="id" />
<!-- @vue-expect-error - label key should type v-model. -->
<UTree v-model="modelValue" :items="itemsWithMappedId" label-key="title" />
</template>
</div>