mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
fix(components): improve generic types (#3331)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user