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

@@ -7,7 +7,14 @@ import _appConfig from '#build/app.config'
import theme from '#build/ui/context-menu'
import { tv } from '../utils/tv'
import type { AvatarProps, KbdProps, LinkProps } from '../types'
import type { DynamicSlots, PartialString, EmitsToProps } from '../types/utils'
import type {
ArrayOrNested,
DynamicSlots,
MergeTypes,
NestedItem,
PartialString,
EmitsToProps
} from '../types/utils'
const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
@@ -36,17 +43,18 @@ export interface ContextMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custo
checked?: boolean
open?: boolean
defaultOpen?: boolean
children?: ContextMenuItem[] | ContextMenuItem[][]
children?: ArrayOrNested<ContextMenuItem>
onSelect?(e: Event): void
onUpdateChecked?(checked: boolean): void
[key: string]: any
}
export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
export interface ContextMenuProps<T extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>> extends Omit<ContextMenuRootProps, 'dir'> {
/**
* @defaultValue 'md'
*/
size?: ContextMenuVariants['size']
items?: T[] | T[][]
items?: T
/**
* The icon displayed when an item is checked.
* @defaultValue appConfig.ui.icons.check
@@ -77,7 +85,7 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
* The key used to get the label from the item.
* @defaultValue 'label'
*/
labelKey?: string
labelKey?: keyof NestedItem<T>
disabled?: boolean
class?: any
ui?: PartialString<typeof contextMenu.slots>
@@ -85,19 +93,22 @@ export interface ContextMenuProps<T> extends Omit<ContextMenuRootProps, 'dir'> {
export interface ContextMenuEmits extends ContextMenuRootEmits {}
type SlotProps<T> = (props: { item: T, active?: boolean, index: number }) => any
type SlotProps<T extends ContextMenuItem> = (props: { item: T, active?: boolean, index: number }) => any
export type ContextMenuSlots<T extends { slot?: string }> = {
export type ContextMenuSlots<
A extends ArrayOrNested<ContextMenuItem> = ArrayOrNested<ContextMenuItem>,
T extends NestedItem<A> = NestedItem<A>
> = {
'default'(props?: {}): any
'item': SlotProps<T>
'item-leading': SlotProps<T>
'item-label': SlotProps<T>
'item-trailing': SlotProps<T>
} & DynamicSlots<T, SlotProps<T>>
} & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing', { active?: boolean, index: number }>
</script>
<script setup lang="ts" generic="T extends ContextMenuItem">
<script setup lang="ts" generic="T extends ArrayOrNested<ContextMenuItem>">
import { computed, toRef } from 'vue'
import { ContextMenuRoot, ContextMenuTrigger, useForwardPropsEmits } from 'reka-ui'
import { reactivePick } from '@vueuse/core'
@@ -114,8 +125,9 @@ const emits = defineEmits<ContextMenuEmits>()
const slots = defineSlots<ContextMenuSlots<T>>()
const rootProps = useForwardPropsEmits(reactivePick(props, 'modal'), emits)
const contentProps = toRef(() => props.content)
const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
const proxySlots = omit(slots, ['default'])
const ui = computed(() => contextMenu({
size: props.size
@@ -135,13 +147,13 @@ const ui = computed(() => contextMenu({
v-bind="contentProps"
:items="items"
:portal="portal"
:label-key="labelKey"
:label-key="(labelKey as keyof NestedItem<T>)"
:checked-icon="checkedIcon"
:loading-icon="loadingIcon"
:external-icon="externalIcon"
>
<template v-for="(_, name) in proxySlots" #[name]="slotData">
<slot :name="name" v-bind="slotData" />
<slot :name="(name as keyof ContextMenuSlots<T>)" v-bind="slotData" />
</template>
</UContextMenuContent>
</ContextMenuRoot>