Skip to content

Commit

Permalink
feat(Button): handle avatar prop
Browse files Browse the repository at this point in the history
  • Loading branch information
benjamincanac committed Oct 17, 2024
1 parent 716ed10 commit a54c3e4
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 1 deletion.
34 changes: 34 additions & 0 deletions docs/content/3.components/button.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
40 changes: 39 additions & 1 deletion playground/app/pages/components/button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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>
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/components/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/composables/useComponentIcons.ts
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down
7 changes: 7 additions & 0 deletions src/theme/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default (options: Required<ModuleOptions>) => ({
label: 'truncate',
leadingIcon: 'shrink-0',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
trailingIcon: 'shrink-0'
},
variants: {
Expand All @@ -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'
}
},
Expand Down
6 changes: 6 additions & 0 deletions test/components/Button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } }],
Expand Down
40 changes: 40 additions & 0 deletions test/components/__snapshots__/Button.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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>
Expand Down Expand Up @@ -65,13 +85,33 @@ 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-->
<!--v-if-->
</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-->
Expand Down

0 comments on commit a54c3e4

Please sign in to comment.