Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/content/3.components/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ Use the `items` prop as an array of objects with the following properties:
- `label?: string`{lang="ts-type"}
- `icon?: string`{lang="ts-type"}
- `avatar?: AvatarProps`{lang="ts-type"}
- `badge?: string | number | BadgeProps`{lang="ts-type"}
- `content?: string`{lang="ts-type"}
- `value?: string | number`{lang="ts-type"}
- `disabled?: boolean`{lang="ts-type"}
- [`slot?: string`{lang="ts-type"}](#with-custom-slot)
- `class?: any`{lang="ts-type"}
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, label?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}
- `ui?: { trigger?: ClassNameValue, leadingIcon?: ClassNameValue, leadingAvatar?: ClassNameValue, leadingAvatarSize?: ClassNameValue, label?: ClassNameValue, trailingBadge?: ClassNameValue, trailingBadgeSize?: ClassNameValue, content?: ClassNameValue }`{lang="ts-type"}

::component-code
---
Expand Down
3 changes: 2 additions & 1 deletion playground/app/pages/components/tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const items = [{
label: 'Tab3',
icon: 'i-lucide-bell',
content: 'Finally, this is the content for Tab3',
slot: 'custom' as const
slot: 'custom' as const,
badge: '300'
}]
</script>

Expand Down
22 changes: 18 additions & 4 deletions src/runtime/components/Tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { TabsRootProps, TabsRootEmits } from 'reka-ui'
import type { AppConfig } from '@nuxt/schema'
import theme from '#build/ui/tabs'
import type { AvatarProps } from '../types'
import type { AvatarProps, BadgeProps } from '../types'
import type { DynamicSlots, ComponentConfig } from '../types/utils'

type Tabs = ComponentConfig<typeof theme, AppConfig, 'tabs'>
Expand All @@ -15,13 +15,18 @@ export interface TabsItem {
*/
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' | 'label' | 'content'>
ui?: Pick<Tabs['slots'], 'trigger' | 'leadingIcon' | 'leadingAvatar' | 'leadingAvatarSize' | 'label' | 'trailingBadge' | 'trailingBadgeSize' | 'content'>
[key: string]: any
}

Expand Down Expand Up @@ -134,14 +139,23 @@ defineExpose({
>
<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="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="ui.leadingAvatar({ class: [props.ui?.leadingAvatar, item.ui?.leadingAvatar] })" />
<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" />
<slot name="trailing" :item="item" :index="index">
<UBadge
v-if="item.badge"
Copy link
Contributor

@MickL MickL Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe v-if="item.badge !== undefined" ? Because if badge is 0 it doesnt get displayed even tho it may be intended to display a zero.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MickL Indeed! Fixed in: b22891a

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" />
Expand Down
6 changes: 4 additions & 2 deletions src/theme/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ export default (options: Required<ModuleOptions>) => ({
list: 'relative flex p-1 group',
indicator: 'absolute transition-[translate,width] duration-200',
trigger: ['group relative inline-flex items-center min-w-0 data-[state=inactive]:text-muted hover:data-[state=inactive]:not-disabled:text-default font-medium rounded-md disabled:cursor-not-allowed disabled:opacity-75', options.theme.transitions && 'transition-colors'],
content: 'focus:outline-none w-full',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
label: 'truncate'
label: 'truncate',
trailingBadge: 'shrink-0',
trailingBadgeSize: 'sm',
content: 'focus:outline-none w-full'
},
variants: {
color: {
Expand Down
3 changes: 2 additions & 1 deletion test/components/Tabs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ describe('Tabs', () => {
label: 'Tab3',
icon: 'i-lucide-bell',
content: 'Finally, this is the content for Tab3',
slot: 'custom'
slot: 'custom',
badge: 'badge'
}]

const props = { items }
Expand Down
183 changes: 160 additions & 23 deletions test/components/__snapshots__/Tabs-vue.spec.ts.snap

Large diffs are not rendered by default.

Loading