Skip to content

Commit dd81d46

Browse files
feat(components): add data-slot attributes (#5447)
1 parent 5f0a107 commit dd81d46

File tree

276 files changed

+44998
-44799
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

276 files changed

+44998
-44799
lines changed

cli/templates.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
6262
</script>
6363
6464
<template>
65-
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
65+
<Primitive :as="as" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
6666
<slot />
6767
</Primitive>
6868
</template>
@@ -105,7 +105,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.${camelName}
105105
</script>
106106
107107
<template>
108-
<${upperName}Root v-bind="rootProps" :class="ui.root({ class: [props.ui?.root, props.class] })" />
108+
<${upperName}Root v-bind="rootProps" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })" />
109109
</template>
110110
`
111111
}

src/runtime/components/Accordion.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,34 +93,35 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.accordion ||
9393
</script>
9494

9595
<template>
96-
<AccordionRoot v-bind="rootProps" :type="type" :class="ui.root({ class: [props.ui?.root, props.class] })">
96+
<AccordionRoot v-bind="rootProps" :type="type" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
9797
<AccordionItem
9898
v-for="(item, index) in props.items"
9999
v-slot="{ open }"
100100
:key="index"
101101
:value="item.value || String(index)"
102102
:disabled="item.disabled"
103+
data-slot="item"
103104
:class="ui.item({ class: [props.ui?.item, item.ui?.item, item.class] })"
104105
>
105-
<AccordionHeader as="div" :class="ui.header({ class: [props.ui?.header, item.ui?.header] })">
106-
<AccordionTrigger :class="ui.trigger({ class: [props.ui?.trigger, item.ui?.trigger], disabled: item.disabled })">
106+
<AccordionHeader as="div" data-slot="header" :class="ui.header({ class: [props.ui?.header, item.ui?.header] })">
107+
<AccordionTrigger data-slot="trigger" :class="ui.trigger({ class: [props.ui?.trigger, item.ui?.trigger], disabled: item.disabled })">
107108
<slot name="leading" :item="item" :index="index" :open="open" :ui="ui">
108-
<UIcon v-if="item.icon" :name="item.icon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item?.ui?.leadingIcon] })" />
109+
<UIcon v-if="item.icon" :name="item.icon" data-slot="leadingIcon" :class="ui.leadingIcon({ class: [props.ui?.leadingIcon, item?.ui?.leadingIcon] })" />
109110
</slot>
110111

111-
<span v-if="get(item, props.labelKey as string) || !!slots.default" :class="ui.label({ class: [props.ui?.label, item.ui?.label] })">
112+
<span v-if="get(item, props.labelKey as string) || !!slots.default" data-slot="label" :class="ui.label({ class: [props.ui?.label, item.ui?.label] })">
112113
<slot :item="item" :index="index" :open="open">{{ get(item, props.labelKey as string) }}</slot>
113114
</span>
114115

115116
<slot name="trailing" :item="item" :index="index" :open="open" :ui="ui">
116-
<UIcon :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" :class="ui.trailingIcon({ class: [props.ui?.trailingIcon, item.ui?.trailingIcon] })" />
117+
<UIcon :name="item.trailingIcon || trailingIcon || appConfig.ui.icons.chevronDown" data-slot="trailingIcon" :class="ui.trailingIcon({ class: [props.ui?.trailingIcon, item.ui?.trailingIcon] })" />
117118
</slot>
118119
</AccordionTrigger>
119120
</AccordionHeader>
120121

121-
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot as keyof AccordionSlots<T>]) || !!slots.body || (item.slot && !!slots[`${item.slot}-body` as keyof AccordionSlots<T>])" :class="ui.content({ class: [props.ui?.content, item.ui?.content] })">
122+
<AccordionContent v-if="item.content || !!slots.content || (item.slot && !!slots[item.slot as keyof AccordionSlots<T>]) || !!slots.body || (item.slot && !!slots[`${item.slot}-body` as keyof AccordionSlots<T>])" data-slot="content" :class="ui.content({ class: [props.ui?.content, item.ui?.content] })">
122123
<slot :name="((item.slot || 'content') as keyof AccordionSlots<T>)" :item="(item as Extract<T, { slot: string; }>)" :index="index" :open="open" :ui="ui">
123-
<div :class="ui.body({ class: [props.ui?.body, item.ui?.body] })">
124+
<div data-slot="body" :class="ui.body({ class: [props.ui?.body, item.ui?.body] })">
124125
<slot :name="((item.slot ? `${item.slot}-body`: 'body') as keyof AccordionSlots<T>)" :item="(item as Extract<T, { slot: string; }>)" :index="index" :open="open" :ui="ui">
125126
{{ item.content }}
126127
</slot>

src/runtime/components/Alert.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,32 +97,32 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {})
9797
</script>
9898

9999
<template>
100-
<Primitive :as="as" :data-orientation="orientation" :class="ui.root({ class: [props.ui?.root, props.class] })">
100+
<Primitive :as="as" :data-orientation="orientation" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
101101
<slot name="leading" :ui="ui">
102-
<UAvatar v-if="avatar" :size="((props.ui?.avatarSize || ui.avatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
103-
<UIcon v-else-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
102+
<UAvatar v-if="avatar" :size="((props.ui?.avatarSize || ui.avatarSize()) as AvatarProps['size'])" v-bind="avatar" data-slot="avatar" :class="ui.avatar({ class: props.ui?.avatar })" />
103+
<UIcon v-else-if="icon" :name="icon" data-slot="icon" :class="ui.icon({ class: props.ui?.icon })" />
104104
</slot>
105105

106-
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
107-
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
106+
<div data-slot="wrapper" :class="ui.wrapper({ class: props.ui?.wrapper })">
107+
<div v-if="title || !!slots.title" data-slot="title" :class="ui.title({ class: props.ui?.title })">
108108
<slot name="title">
109109
{{ title }}
110110
</slot>
111111
</div>
112-
<div v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
112+
<div v-if="description || !!slots.description" data-slot="description" :class="ui.description({ class: props.ui?.description })">
113113
<slot name="description">
114114
{{ description }}
115115
</slot>
116116
</div>
117117

118-
<div v-if="orientation === 'vertical' && (actions?.length || !!slots.actions)" :class="ui.actions({ class: props.ui?.actions })">
118+
<div v-if="orientation === 'vertical' && (actions?.length || !!slots.actions)" data-slot="actions" :class="ui.actions({ class: props.ui?.actions })">
119119
<slot name="actions">
120120
<UButton v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
121121
</slot>
122122
</div>
123123
</div>
124124

125-
<div v-if="(orientation === 'horizontal' && (actions?.length || !!slots.actions)) || close" :class="ui.actions({ class: props.ui?.actions, orientation: 'horizontal' })">
125+
<div v-if="(orientation === 'horizontal' && (actions?.length || !!slots.actions)) || close" data-slot="actions" :class="ui.actions({ class: props.ui?.actions, orientation: 'horizontal' })">
126126
<template v-if="orientation === 'horizontal' && (actions?.length || !!slots.actions)">
127127
<slot name="actions">
128128
<UButton v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
@@ -137,6 +137,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.alert || {})
137137
variant="link"
138138
:aria-label="t('alert.close')"
139139
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
140+
data-slot="close"
140141
:class="ui.close({ class: props.ui?.close })"
141142
@click="emits('update:open', false)"
142143
/>

src/runtime/components/AuthForm.vue

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,31 +186,31 @@ defineExpose({
186186
</script>
187187

188188
<template>
189-
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
190-
<div v-if="(icon || !!slots.leading) || (title || !!slots.title) || (description || !!slots.description) || !!slots.header" :class="ui.header({ class: props.ui?.header })">
189+
<Primitive :as="as" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
190+
<div v-if="(icon || !!slots.leading) || (title || !!slots.title) || (description || !!slots.description) || !!slots.header" data-slot="header" :class="ui.header({ class: props.ui?.header })">
191191
<slot name="header">
192-
<div v-if="icon || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
192+
<div v-if="icon || !!slots.leading" data-slot="leading" :class="ui.leading({ class: props.ui?.leading })">
193193
<slot name="leading" :ui="ui">
194-
<UIcon v-if="icon" :name="icon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
194+
<UIcon v-if="icon" :name="icon" data-slot="leadingIcon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
195195
</slot>
196196
</div>
197197

198-
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
198+
<div v-if="title || !!slots.title" data-slot="title" :class="ui.title({ class: props.ui?.title })">
199199
<slot name="title">
200200
{{ title }}
201201
</slot>
202202
</div>
203203

204-
<div v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
204+
<div v-if="description || !!slots.description" data-slot="description" :class="ui.description({ class: props.ui?.description })">
205205
<slot name="description">
206206
{{ description }}
207207
</slot>
208208
</div>
209209
</slot>
210210
</div>
211211

212-
<div :class="ui.body({ class: props.ui?.body })">
213-
<div v-if="providers?.length || !!slots.providers" :class="ui.providers({ class: props.ui?.providers })">
212+
<div data-slot="body" :class="ui.body({ class: props.ui?.body })">
213+
<div v-if="providers?.length || !!slots.providers" data-slot="providers" :class="ui.providers({ class: props.ui?.providers })">
214214
<slot name="providers">
215215
<UButton
216216
v-for="(provider, index) in providers"
@@ -226,6 +226,7 @@ defineExpose({
226226
<USeparator
227227
v-if="providers?.length && fields?.length"
228228
v-bind="typeof separator === 'object' ? separator : { label: separator }"
229+
data-slot="separator"
229230
:class="ui.separator({ class: props.ui?.separator })"
230231
/>
231232

@@ -238,6 +239,7 @@ defineExpose({
238239
:validate-on="validateOn"
239240
:disabled="disabled"
240241
:loading-auto="loadingAuto"
242+
data-slot="form"
241243
:class="ui.form({ class: props.ui?.form })"
242244
v-bind="$attrs"
243245
@submit="onSubmit"
@@ -251,19 +253,22 @@ defineExpose({
251253
<UCheckbox
252254
v-if="field.type === 'checkbox'"
253255
v-model="state[field.name]"
256+
data-slot="checkbox"
254257
:class="ui.checkbox({ class: props.ui?.checkbox })"
255258
v-bind="(omitFieldProps(field))"
256259
/>
257260
<USelectMenu
258261
v-else-if="field.type === 'select'"
259262
v-model="state[field.name]"
263+
data-slot="select"
260264
:class="ui.select({ class: props.ui?.select })"
261265
v-bind="(omitFieldProps(field) as AuthFormSelectField)"
262266
/>
263267
<UPinInput
264268
v-else-if="field.type === 'otp'"
265269
:id="field.name"
266270
v-model="state[field.name]"
271+
data-slot="otp"
267272
:class="ui.otp({ class: props.ui?.otp })"
268273
v-bind="(Object.assign({}, omitFieldProps(field), typeof (field as AuthFormOtpField).otp === 'object' ? (field as AuthFormOtpField).otp : {}) as any)"
269274
otp
@@ -272,6 +277,7 @@ defineExpose({
272277
v-else-if="field.type === 'password'"
273278
ref="passwordRef"
274279
v-model="state[field.name]"
280+
data-slot="password"
275281
:class="ui.password({ class: props.ui?.password })"
276282
v-bind="(omitFieldProps(field) as AuthFormInputField<'password'>)"
277283
:type="passwordVisibility ? 'text' : 'password'"
@@ -292,6 +298,7 @@ defineExpose({
292298
<UInput
293299
v-else
294300
v-model="state[field.name]"
301+
data-slot="input"
295302
:class="ui.input({ class: props.ui?.input })"
296303
v-bind="(omitFieldProps(field) as AuthFormInputField)"
297304
/>
@@ -329,7 +336,7 @@ defineExpose({
329336
</UForm>
330337
</div>
331338

332-
<div v-if="!!slots.footer" :class="ui.footer({ class: props.ui?.footer })">
339+
<div v-if="!!slots.footer" data-slot="footer" :class="ui.footer({ class: props.ui?.footer })">
333340
<slot name="footer" />
334341
</div>
335342
</Primitive>

src/runtime/components/Avatar.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ function onError() {
9898
:is="props.chip ? UChip : Primitive"
9999
:as="as.root"
100100
v-bind="props.chip ? (typeof props.chip === 'object' ? { inset: true, ...props.chip } : { inset: true }) : {}"
101+
data-slot="root"
101102
:class="ui.root({ class: [props.ui?.root, props.class] })"
102103
:style="props.style"
103104
>
@@ -109,14 +110,15 @@ function onError() {
109110
:width="sizePx"
110111
:height="sizePx"
111112
v-bind="$attrs"
113+
data-slot="image"
112114
:class="ui.image({ class: props.ui?.image })"
113115
@error="onError"
114116
/>
115117

116118
<Slot v-else v-bind="$attrs">
117119
<slot>
118-
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
119-
<span v-else :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || '&nbsp;' }}</span>
120+
<UIcon v-if="icon" :name="icon" data-slot="icon" :class="ui.icon({ class: props.ui?.icon })" />
121+
<span v-else data-slot="fallback" :class="ui.fallback({ class: props.ui?.fallback })">{{ fallback || '&nbsp;' }}</span>
120122
</slot>
121123
</Slot>
122124
</component>

src/runtime/components/AvatarGroup.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ provide(avatarGroupInjectionKey, computed(() => ({
9393
</script>
9494

9595
<template>
96-
<Primitive :as="as" :class="ui.root({ class: [props.ui?.root, props.class] })">
97-
<UAvatar v-if="hiddenCount > 0" :text="`+${hiddenCount}`" :class="ui.base({ class: props.ui?.base })" />
98-
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" :class="ui.base({ class: props.ui?.base })" />
96+
<Primitive :as="as" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
97+
<UAvatar v-if="hiddenCount > 0" :text="`+${hiddenCount}`" data-slot="base" :class="ui.base({ class: props.ui?.base })" />
98+
<component :is="avatar" v-for="(avatar, count) in visibleAvatars" :key="count" data-slot="base" :class="ui.base({ class: props.ui?.base })" />
9999
</Primitive>
100100
</template>

src/runtime/components/Badge.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,20 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.badge || {})
6868
</script>
6969

7070
<template>
71-
<Primitive :as="as" :class="ui.base({ class: [props.ui?.base, props.class] })">
71+
<Primitive :as="as" data-slot="base" :class="ui.base({ class: [props.ui?.base, props.class] })">
7272
<slot name="leading" :ui="ui">
73-
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
74-
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
73+
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" data-slot="leadingIcon" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
74+
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" data-slot="leadingAvatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
7575
</slot>
7676

7777
<slot :ui="ui">
78-
<span v-if="label !== undefined && label !== null" :class="ui.label({ class: props.ui?.label })">
78+
<span v-if="label !== undefined && label !== null" data-slot="label" :class="ui.label({ class: props.ui?.label })">
7979
{{ label }}
8080
</span>
8181
</slot>
8282

8383
<slot name="trailing" :ui="ui">
84-
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
84+
<UIcon v-if="isTrailing && trailingIconName" :name="trailingIconName" data-slot="trailingIcon" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
8585
</slot>
8686
</Primitive>
8787
</template>

src/runtime/components/Banner.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ function onClose() {
119119
</script>
120120

121121
<template>
122-
<Primitive :as="as" class="banner" :class="ui.root({ class: [props.ui?.root, props.class] })">
122+
<Primitive :as="as" class="banner" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
123123
<ULink
124124
v-if="to"
125125
:aria-label="title"
@@ -131,28 +131,28 @@ function onClose() {
131131
<span class="absolute inset-0 " aria-hidden="true" />
132132
</ULink>
133133

134-
<UContainer :class="ui.container({ class: props.ui?.container })">
135-
<div :class="ui.left({ class: props.ui?.left })" />
134+
<UContainer data-slot="container" :class="ui.container({ class: props.ui?.container })">
135+
<div data-slot="left" :class="ui.left({ class: props.ui?.left })" />
136136

137-
<div :class="ui.center({ class: props.ui?.center })">
137+
<div data-slot="center" :class="ui.center({ class: props.ui?.center })">
138138
<slot name="leading" :ui="ui">
139-
<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: props.ui?.icon })" />
139+
<UIcon v-if="icon" :name="icon" data-slot="icon" :class="ui.icon({ class: props.ui?.icon })" />
140140
</slot>
141141

142-
<div v-if="title || !!slots.title" :class="ui.title({ class: props.ui?.title })">
142+
<div v-if="title || !!slots.title" data-slot="title" :class="ui.title({ class: props.ui?.title })">
143143
<slot name="title">
144144
{{ title }}
145145
</slot>
146146
</div>
147147

148-
<div v-if="actions?.length || !!slots.actions" :class="ui.actions({ class: props.ui?.actions })">
148+
<div v-if="actions?.length || !!slots.actions" data-slot="actions" :class="ui.actions({ class: props.ui?.actions })">
149149
<slot name="actions">
150150
<UButton v-for="(action, index) in actions" :key="index" color="neutral" size="xs" v-bind="action" />
151151
</slot>
152152
</div>
153153
</div>
154154

155-
<div :class="ui.right({ class: props.ui?.right })">
155+
<div data-slot="right" :class="ui.right({ class: props.ui?.right })">
156156
<slot name="close" :ui="ui">
157157
<UButton
158158
v-if="close"
@@ -162,6 +162,7 @@ function onClose() {
162162
variant="ghost"
163163
:aria-label="t('banner.close')"
164164
v-bind="(typeof close === 'object' ? close as Partial<ButtonProps> : {})"
165+
data-slot="close"
165166
:class="ui.close({ class: props.ui?.close })"
166167
@click="onClose"
167168
/>

0 commit comments

Comments
 (0)