feat(Button): handle avatar prop

This commit is contained in:
Benjamin Canac
2024-10-17 17:09:59 +02:00
parent 716ed10068
commit a54c3e49fe
7 changed files with 131 additions and 1 deletions

View File

@@ -125,6 +125,40 @@ props:
---
::
### Avatar
Use the `avatar` prop to show an [Avatar](/components/avatar) inside the Button.
::component-code
---
prettier: true
props:
avatar:
src: 'https://github.com/nuxt.png'
size: md
color: neutral
variant: outline
slots:
default: |
Button
---
::
The `label` as prop or slot is optional so you can use the Button as an avatar-only button.
::component-code
---
prettier: true
props:
avatar:
src: 'https://github.com/nuxt.png'
size: md
color: neutral
variant: outline
---
::
### Loading
Use the `loading` prop to show a loading icon and disable the Button.

View File

@@ -32,10 +32,15 @@ function onClick() {
</UButton>
</div>
<div class="flex items-center gap-2">
<UButton loading-auto @click="onClick">
<UButton loading>
Loading
</UButton>
</div>
<div class="flex items-center gap-2">
<UButton loading-auto @click="onClick">
Loading auto
</UButton>
</div>
<div class="flex items-center gap-2">
<UButton loading trailing>
Loading
@@ -54,12 +59,25 @@ function onClick() {
color="neutral"
/>
</div>
<div class="flex items-center gap-2">
<UButton
v-for="variant in variants"
:key="variant"
:avatar="{ src: 'https://github.com/benjamincanac.png' }"
:label="upperFirst(variant)"
color="neutral"
:variant="variant"
/>
</div>
<div class="flex items-center gap-2 ml-[-129px]">
<UButton v-for="size in sizes" :key="size" label="Button" :size="size" />
</div>
<div class="flex items-center gap-2 ml-[-171px]">
<UButton v-for="size in sizes" :key="size" icon="i-heroicons-rocket-launch" label="Button" :size="size" />
</div>
<div class="flex items-center gap-2 ml-[-171px]">
<UButton v-for="size in sizes" :key="size" :avatar="{ src: 'https://github.com/benjamincanac.png' }" label="Button" :size="size" />
</div>
<div class="flex items-center gap-2 ml-[-159px]">
<UButton
v-for="size in sizes"
@@ -70,9 +88,29 @@ function onClick() {
:size="size"
/>
</div>
<div class="flex items-center gap-2 ml-[-159px]">
<UButton
v-for="size in sizes"
:key="size"
:avatar="{ src: 'https://github.com/benjamincanac.png' }"
label="Square"
square
:size="size"
/>
</div>
<div class="flex items-center gap-2 ml-[-68px]">
<UButton v-for="size in sizes" :key="size" icon="i-heroicons-rocket-launch" :size="size" />
</div>
<div class="flex items-center gap-2 ml-[-68px]">
<UButton
v-for="size in sizes"
:key="size"
:avatar="{ src: 'https://github.com/benjamincanac.png' }"
:size="size"
color="neutral"
variant="outline"
/>
</div>
<div class="flex items-center gap-2">
<UButton icon="i-heroicons-rocket-launch" trailing-icon="i-heroicons-chevron-down-20-solid" label="Block" block />
</div>

View File

@@ -5,6 +5,7 @@ import _appConfig from '#build/app.config'
import theme from '#build/ui/button'
import type { LinkProps } from './Link.vue'
import type { UseComponentIconsProps } from '../composables/useComponentIcons'
import type { AvatarProps } from '../types'
import type { PartialString } from '../types/utils'
import { formLoadingInjectionKey } from '../composables/useFormField'
@@ -99,6 +100,7 @@ const ui = computed(() => button({
>
<slot name="leading">
<UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
<UAvatar v-else-if="!!avatar" :size="((props.ui?.leadingAvatarSize || ui.leadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.leadingAvatar({ class: props.ui?.leadingAvatar })" />
</slot>
<slot>

View File

@@ -1,9 +1,12 @@
import { computed, toValue, type MaybeRefOrGetter } from 'vue'
import { useAppConfig } from '#imports'
import type { AvatarProps } from '../types'
export interface UseComponentIconsProps {
/** Display an icon based on the `leading` and `trailing` props. */
icon?: string
/** Display an avatar on the left side. */
avatar?: AvatarProps
/** When `true`, the icon will be displayed on the left side. */
leading?: boolean
/** Display an icon on the left side. */

View File

@@ -7,6 +7,7 @@ export default (options: Required<ModuleOptions>) => ({
label: 'truncate',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
trailingIcon: 'shrink-0'
},
variants: {
@@ -27,32 +28,38 @@ export default (options: Required<ModuleOptions>) => ({
xs: {
base: 'px-2 py-1 text-xs gap-1',
leadingIcon: 'size-4',
leadingAvatarSize: '3xs',
trailingIcon: 'size-4'
},
sm: {
base: 'px-2.5 py-1.5 text-xs gap-1.5',
leadingIcon: 'size-4',
leadingAvatarSize: '3xs',
trailingIcon: 'size-4'
},
md: {
base: 'px-2.5 py-1.5 text-sm gap-1.5',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs',
trailingIcon: 'size-5'
},
lg: {
base: 'px-3 py-2 text-sm gap-2',
leadingIcon: 'size-5',
leadingAvatarSize: '2xs',
trailingIcon: 'size-5'
},
xl: {
base: 'px-3 py-2 text-base gap-2',
leadingIcon: 'size-6',
leadingAvatarSize: 'xs',
trailingIcon: 'size-6'
}
},
block: {
true: {
base: 'w-full justify-center',
leadingAvatarSize: 'xs',
trailingIcon: 'ms-auto'
}
},

View File

@@ -25,7 +25,13 @@ describe('Button', () => {
['with leadingIcon', { props: { leadingIcon: 'i-heroicons-arrow-left' } }],
['with trailing and icon', { props: { trailing: true, icon: 'i-heroicons-arrow-right' } }],
['with trailingIcon', { props: { trailingIcon: 'i-heroicons-arrow-right' } }],
['with avatar', { props: { avatar: { src: 'https://github.com/benjamincanac.png' } } }],
['with avatar and leadingIcon', { props: { avatar: { src: 'https://github.com/benjamincanac.png' }, leadingIcon: 'i-heroicons-arrow-left' } }],
['with avatar and trailingIcon', { props: { avatar: { src: 'https://github.com/benjamincanac.png' }, trailingIcon: 'i-heroicons-arrow-right' } }],
['with loading', { props: { loading: true } }],
['with loading and avatar', { props: { loading: true, avatar: { src: 'https://github.com/benjamincanac.png' } } }],
['with loading trailing', { props: { loading: true, trailing: true } }],
['with loading trailing and avatar', { props: { loading: true, trailing: true, avatar: { src: 'https://github.com/benjamincanac.png' } } }],
['with loadingIcon', { props: { loading: true, loadingIcon: 'i-heroicons-sparkles' } }],
['with disabled', { props: { label: 'Button', disabled: true } }],
['with disabled and with link', { props: { label: 'Button', disabled: true, to: '/link' } }],

View File

@@ -1,5 +1,25 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Button > renders with avatar and leadingIcon correctly 1`] = `
"<button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="iconify i-heroicons:arrow-left shrink-0 size-5" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button>"
`;
exports[`Button > renders with avatar and trailingIcon correctly 1`] = `
"<button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"></span></span>
<!--v-if--><span class="iconify i-heroicons:arrow-right shrink-0 size-5" aria-hidden="true"></span>
</button>"
`;
exports[`Button > renders with avatar correctly 1`] = `
"<button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"></span></span>
<!--v-if-->
<!--v-if-->
</button>"
`;
exports[`Button > renders with block correctly 1`] = `
"<button type="button" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors px-2.5 py-1.5 text-sm gap-1.5 w-full justify-center text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)]">
<!--v-if--><span class="truncate">Button</span>
@@ -65,6 +85,13 @@ exports[`Button > renders with leadingIcon correctly 1`] = `
</button>"
`;
exports[`Button > renders with loading and avatar correctly 1`] = `
"<button type="button" disabled="" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="iconify i-heroicons:arrow-path-20-solid shrink-0 size-5 animate-spin" aria-hidden="true"></span>
<!--v-if-->
<!--v-if-->
</button>"
`;
exports[`Button > renders with loading correctly 1`] = `
"<button type="button" disabled="" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="iconify i-heroicons:arrow-path-20-solid shrink-0 size-5 animate-spin" aria-hidden="true"></span>
<!--v-if-->
@@ -72,6 +99,19 @@ exports[`Button > renders with loading correctly 1`] = `
</button>"
`;
exports[`Button > renders with loading trailing and avatar correctly 1`] = `
"<button type="button" disabled="" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="inline-flex items-center justify-center select-none overflow-hidden rounded-full align-middle bg-[var(--ui-bg-elevated)] size-5 text-[10px] shrink-0"><img role="img" src="https://github.com/benjamincanac.png" class="h-full w-full rounded-[inherit] object-cover" style="display: none;"><span class="font-medium leading-none text-[var(--ui-text-muted)] truncate"></span></span>
<!--v-if--><span class="iconify i-heroicons:arrow-path-20-solid shrink-0 size-5 animate-spin" aria-hidden="true"></span>
</button>"
`;
exports[`Button > renders with loading trailing correctly 1`] = `
"<button type="button" disabled="" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5">
<!--v-if-->
<!--v-if--><span class="iconify i-heroicons:arrow-path-20-solid shrink-0 size-5 animate-spin" aria-hidden="true"></span>
</button>"
`;
exports[`Button > renders with loadingIcon correctly 1`] = `
"<button type="button" disabled="" class="rounded-[calc(var(--ui-radius)*1.5)] font-medium inline-flex items-center focus:outline-none disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75 transition-colors text-sm gap-1.5 text-[var(--ui-bg)] bg-[var(--ui-primary)] hover:bg-[var(--ui-primary)]/75 disabled:bg-[var(--ui-primary)] aria-disabled:bg-[var(--ui-primary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ui-primary)] p-1.5"><span class="iconify i-heroicons:sparkles shrink-0 size-5 animate-spin" aria-hidden="true"></span>
<!--v-if-->