Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | <template>
<button
:type="type"
:class="buttonClasses"
:disabled="disabled || loading"
@click="emit('click', $event)"
>
<span v-if="loading" class="mr-2 animate-spin">
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
</span>
<slot />
</button>
</template>
<script setup lang="ts">
/**
* BaseButton
* @description Reusable button component with variants and loading state
*/
interface Props {
/** Button variant style */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
/** Button size */
size?: 'sm' | 'md' | 'lg'
/** HTML button type */
type?: 'button' | 'submit' | 'reset'
/** Disabled state */
disabled?: boolean
/** Loading state with spinner */
loading?: boolean
}
interface Emits {
(e: 'click', event: MouseEvent): void
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
type: 'button',
disabled: false,
loading: false
})
const emit = defineEmits<Emits>()
const buttonClasses = computed(() => {
const base = [
'inline-flex items-center justify-center font-medium rounded-lg',
'transition-all duration-200',
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-opacity-50'
]
const sizes = {
sm: 'px-3 py-2 text-sm min-h-[44px]',
md: 'px-4 py-2.5 text-base min-h-[44px]',
lg: 'px-6 py-3 text-lg min-h-[48px]'
}
const variants = {
primary: [
'bg-primary-600 text-white',
'hover:bg-primary-700',
'focus-visible:ring-primary-500',
'active:bg-primary-800'
],
secondary: [
'bg-secondary-200 text-secondary-800',
'hover:bg-secondary-300',
'focus-visible:ring-secondary-500',
'active:bg-secondary-400'
],
ghost: [
'bg-transparent text-secondary-700',
'hover:bg-secondary-100',
'focus-visible:ring-secondary-500',
'active:bg-secondary-200'
],
danger: [
'bg-error-600 text-white',
'hover:bg-error-700',
'focus-visible:ring-error-500',
'active:bg-error-800'
]
}
const disabled = (props.disabled || props.loading)
? 'opacity-50 cursor-not-allowed'
: 'cursor-pointer'
return [...base, sizes[props.size], ...variants[props.variant], disabled]
})
</script>
|