mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
173 lines
5.9 KiB
Vue
173 lines
5.9 KiB
Vue
<!-- eslint-disable vue/block-tag-newline -->
|
|
<script lang="ts">
|
|
import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
|
|
import type { AppConfig } from '@nuxt/schema'
|
|
import theme from '#build/ui/tabs'
|
|
import type { AvatarProps, BadgeProps } from '../types'
|
|
import type { DynamicSlots, ComponentConfig } from '../types/utils'
|
|
|
|
type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'>
|
|
|
|
export interface TabsItem {
|
|
label?: string
|
|
/**
|
|
* @IconifyIcon
|
|
*/
|
|
icon?: string
|
|
avatar?: AvatarProps
|
|
/**
|
|
* Display a badge on the item.
|
|
* `{ size: 'sm', color: 'neutral', variant: 'outline' }`{lang="ts-type"}
|
|
*/
|
|
badge?: string | number | BadgeProps
|
|
slot?: string
|
|
content?: string
|
|
/** A unique value for the tab item. Defaults to the index. */
|
|
value?: string | number
|
|
disabled?: boolean
|
|
class?: any
|
|
ui?: Pick<Tabs['slots'], 'trigger' | 'leadingIcon' | 'leadingAvatar' | 'leadingAvatarSize' | 'label' | 'trailingBadge' | 'trailingBadgeSize' | 'content'>
|
|
[key: string]: any
|
|
}
|
|
|
|
export interface TabsProps<T extends TabsItem = TabsItem> extends Pick<TabsRootProps<string | number>, 'defaultValue' | 'modelValue' | 'activationMode' | 'unmountOnHide'> {
|
|
/**
|
|
* The element or component this component should render as.
|
|
* @defaultValue 'div'
|
|
*/
|
|
as?: any
|
|
items?: T[]
|
|
/**
|
|
* @defaultValue 'primary'
|
|
*/
|
|
color?: Tabs['variants']['color']
|
|
/**
|
|
* @defaultValue 'pill'
|
|
*/
|
|
variant?: Tabs['variants']['variant']
|
|
/**
|
|
* @defaultValue 'md'
|
|
*/
|
|
size?: Tabs['variants']['size']
|
|
/**
|
|
* The orientation of the tabs.
|
|
* @defaultValue 'horizontal'
|
|
*/
|
|
orientation?: TabsRootProps['orientation']
|
|
/**
|
|
* The content of the tabs, can be disabled to prevent rendering the content.
|
|
* @defaultValue true
|
|
*/
|
|
content?: boolean
|
|
/**
|
|
* The key used to get the label from the item.
|
|
* @defaultValue 'label'
|
|
*/
|
|
labelKey?: string
|
|
class?: any
|
|
ui?: Tabs['slots']
|
|
}
|
|
|
|
export interface TabsEmits extends TabsRootEmits<string | number> {}
|
|
|
|
type SlotProps<T extends TabsItem> = (props: { item: T, index: number }) => any
|
|
|
|
export type TabsSlots<T extends TabsItem = TabsItem> = {
|
|
'leading': SlotProps<T>
|
|
'default': SlotProps<T>
|
|
'trailing': SlotProps<T>
|
|
'content': SlotProps<T>
|
|
'list-leading': (props?: {}) => any
|
|
'list-trailing': (props?: {}) => any
|
|
} & DynamicSlots<T, undefined, { index: number }>
|
|
|
|
</script>
|
|
|
|
<script setup lang="ts" generic="T extends TabsItem">
|
|
import type { ComponentPublicInstance } from 'vue'
|
|
import { ref, computed } from 'vue'
|
|
import { TabsRoot, TabsList, TabsIndicator, TabsTrigger, TabsContent, useForwardPropsEmits } from 'reka-ui'
|
|
import { reactivePick } from '@vueuse/core'
|
|
import { useAppConfig } from '#imports'
|
|
import { get } from '../utils'
|
|
import { tv } from '../utils/tv'
|
|
import UIcon from './Icon.vue'
|
|
import UAvatar from './Avatar.vue'
|
|
|
|
const props = withDefaults(defineProps<TabsProps<T>>(), {
|
|
content: true,
|
|
defaultValue: '0',
|
|
orientation: 'horizontal',
|
|
unmountOnHide: true,
|
|
labelKey: 'label'
|
|
})
|
|
const emits = defineEmits<TabsEmits>()
|
|
const slots = defineSlots<TabsSlots<T>>()
|
|
|
|
const appConfig = useAppConfig() as Tabs['AppConfig']
|
|
|
|
const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'orientation', 'activationMode', 'unmountOnHide'), emits)
|
|
|
|
const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.tabs || {}) })({
|
|
color: props.color,
|
|
variant: props.variant,
|
|
size: props.size,
|
|
orientation: props.orientation
|
|
}))
|
|
|
|
const triggersRef = ref<ComponentPublicInstance[]>([])
|
|
|
|
defineExpose({
|
|
triggersRef
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<TabsRoot v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })">
|
|
<TabsList :class="ui.list({ class: props.ui?.list })">
|
|
<TabsIndicator :class="ui.indicator({ class: props.ui?.indicator })" />
|
|
|
|
<slot name="list-leading" />
|
|
|
|
<TabsTrigger
|
|
v-for="(item, index) of items"
|
|
:key="index"
|
|
:ref="el => (triggersRef[index] = el as ComponentPublicInstance)"
|
|
:value="item.value || String(index)"
|
|
:disabled="item.disabled"
|
|
:class="ui.trigger({ class: [props.ui?.trigger, item.ui?.trigger] })"
|
|
>
|
|
<slot name="leading" :item="item" :index="index">
|
|
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item.ui?.leadingIcon] })" />
|
|
<UAvatar v-else-if="item.avatar" :size="((item.ui?.leadingAvatarSize || props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: [props.ui?.leadingAvatar, item.ui?.leadingAvatar] })" />
|
|
</slot>
|
|
|
|
<span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: [props.ui?.label, item.ui?.label] })">
|
|
<slot :item="item" :index="index">{{ get(item, props.labelKey as string) }}</slot>
|
|
</span>
|
|
|
|
<slot name="trailing" :item="item" :index="index">
|
|
<UBadge
|
|
v-if="item.badge"
|
|
color="neutral"
|
|
variant="outline"
|
|
:size="((item.ui?.trailingBadgeSize || props.ui?.trailingBadgeSize || ui.trailingBadgeSize()) as BadgeProps['size'])"
|
|
v-bind="(typeof item.badge === 'string' || typeof item.badge === 'number') ? { label: item.badge } : item.badge"
|
|
:class="ui.trailingBadge({ class: [props.ui?.trailingBadge, item.ui?.trailingBadge] })"
|
|
/>
|
|
</slot>
|
|
</TabsTrigger>
|
|
|
|
<slot name="list-trailing" />
|
|
</TabsList>
|
|
|
|
<template v-if="!!content">
|
|
<TabsContent v-for="(item, index) of items" :key="index" :value="item.value || String(index)" :class="ui.content({ class: [props.ui?.content, item.ui?.content, item.class] })">
|
|
<slot :name="((item.slot || 'content') as keyof TabsSlots<T>)" :item="(item as Extract<T, { slot: string; }>)" :index="index">
|
|
{{ item.content }}
|
|
</slot>
|
|
</TabsContent>
|
|
</template>
|
|
</TabsRoot>
|
|
</template>
|