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>