vue-button — Vue 3 button SFC s emit click event a loading stavem

resources/js/components/VButton.vue
<template>
	<button
		:type="type"
		:disabled="disabled || loading"
		:class="['v-btn', `v-btn--${variant}`, `v-btn--${size}`]"
		@click="onClick"
	>
		<span v-if="loading" class="v-btn__spinner" aria-hidden="true"></span>
		<span class="v-btn__label">
			<slot>{{ label }}</slot>
		</span>
	</button>
</template>

<script setup>
defineProps({
	label: { type: String, default: '' },
	variant: { type: String, default: 'primary' },
	size: { type: String, default: 'md' },
	type: { type: String, default: 'button' },
	disabled: { type: Boolean, default: false },
	loading: { type: Boolean, default: false },
})

const emit = defineEmits(['click'])

const onClick = (e) => emit('click', e)
</script>

<style lang="scss" scoped>
@import 'components/base/variables';
@import 'components/base/mixins';

.v-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	border: none;
	border-radius: 4px;
	cursor: pointer;
	font-weight: 600;
	transition: background 0.15s, color 0.15s;

	&--primary {
		background: $cgui-color-primary;
		color: white;

		@include cgui-hover() {
			background: darken($cgui-color-primary, 8%);
		}
	}

	&--ghost {
		background: transparent;
		color: $cgui-color-text;
		border: 1px solid $cgui-color-border;
	}

	&--sm { padding: 4px 10px; font-size: 0.85em; }
	&--md { padding: 8px 16px; font-size: 1em; }
	&--lg { padding: 12px 24px; font-size: 1.1em; }

	&[disabled] {
		opacity: 0.5;
		cursor: not-allowed;
	}

	&__spinner {
		width: 14px;
		height: 14px;
		border: 2px solid currentColor;
		border-right-color: transparent;
		border-radius: 50%;
		animation: v-btn-spin 0.6s linear infinite;
	}
}

@keyframes v-btn-spin {
	to { transform: rotate(360deg); }
}
</style>