Skip to content
Closed
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
36 changes: 35 additions & 1 deletion src/runtime/components/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@ import UAvatar from './Avatar.vue'
import ULink from './Link.vue'
import ULinkBase from './LinkBase.vue'

defineOptions({ inheritAttrs: false })

const props = defineProps<ButtonProps>()
const slots = defineSlots<ButtonSlots>()

const appConfig = useAppConfig() as Button['AppConfig']
const uiProp = useComponentUI('button', props)
const { orientation, size: buttonSize } = useFieldGroup<ButtonProps>(props)

const isLink = computed(() => !!(props.to || props.href))
const linkProps = useForwardProps(pickLinkProps(props))

const loadingAutoState = ref(false)
Expand Down Expand Up @@ -118,10 +121,11 @@ const ui = computed(() => tv({

<template>
<ULink
v-if="isLink"
v-slot="{ active, ...slotProps }"
v-bind="{ ...$attrs, ...omit(linkProps, ['type', 'disabled', 'onClick']) }"
:type="type"
:disabled="disabled || isLoading"
v-bind="omit(linkProps, ['type', 'disabled', 'onClick'])"
custom
>
Comment on lines 123 to 130
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

Add data-slot on the link-branch root element.

Line 123 renders ULink without a data-slot attribute, while the non-link root already has one.

Proposed fix
   <ULink
+    data-slot="link"
     v-if="isLink"

As per coding guidelines: "Add data-slot="name" attributes on all template elements for slot identification".

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Button.vue` around lines 123 - 130, The link branch
rendering of ULink is missing the required data-slot attribute; update the ULink
root element (the template branch where isLink is true and ULink is used) to
include data-slot="name" (matching the naming used elsewhere) alongside the
existing props (v-slot, v-bind, :type, :disabled, custom) so the link branch has
the same slot identification attribute as the non-link root; ensure the
attribute is added on the ULink tag (the component used with v-slot="{ active,
...slotProps }") and preserved when merging $attrs/omit(linkProps).

<ULinkBase
Expand Down Expand Up @@ -151,4 +155,34 @@ const ui = computed(() => tv({
</slot>
</ULinkBase>
</ULink>
<ULinkBase
v-else
v-bind="$attrs"
:as="as"
:type="type"
:disabled="disabled || isLoading"
data-slot="base"
:class="ui.base({
class: [uiProp?.base, props.class],
active: active ?? false,
...(active && activeVariant ? { variant: activeVariant } : {}),
...(active && activeColor ? { color: activeColor } : {})
})"
@click="onClickWrapper"
>
<slot name="leading" :ui="ui">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" data-slot="leadingIcon" :class="ui.leadingIcon({ class: uiProp?.leadingIcon, active: active ?? false })" />
<UAvatar v-else-if="!!avatar" :size="((uiProp?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" data-slot="leadingAvatar" :class="ui.leadingAvatar({ class: uiProp?.leadingAvatar, active: active ?? false })" />
</slot>

<slot :ui="ui">
<span v-if="label !== undefined && label !== null" data-slot="label" :class="ui.label({ class: uiProp?.label, active: active ?? false })">
{{ label }}
</span>
</slot>

<slot name="trailing" :ui="ui">
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" data-slot="trailingIcon" :class="ui.trailingIcon({ class: uiProp?.trailingIcon, active: active ?? false })" />
</slot>
</ULinkBase>
</template>
33 changes: 33 additions & 0 deletions test/components/Button.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineComponent, h } from 'vue'
import { describe, bench } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import Button from '../../src/runtime/components/Button.vue'

const BUTTON_COUNT = 500

function createButtonList(props: Record<string, any>) {
return defineComponent({
setup() {
return () => h('div', Array.from({ length: BUTTON_COUNT }, (_, i) =>
h(Button, { key: i, label: `Button ${i}`, ...props })
))
}
})
}

describe('Button mount performance', () => {
bench(`${BUTTON_COUNT} plain buttons`, async () => {
const wrapper = await mountSuspended(createButtonList({}))
wrapper.unmount()
})

bench(`${BUTTON_COUNT} link buttons`, async () => {
const wrapper = await mountSuspended(createButtonList({ to: '/test' }))
wrapper.unmount()
})

bench(`${BUTTON_COUNT} plain buttons with icon`, async () => {
const wrapper = await mountSuspended(createButtonList({ icon: 'i-lucide-rocket' }))
wrapper.unmount()
})
})
Loading