| <template> |
| <button |
| :class="buttonClasses" |
| :disabled="disabled || loading" |
| @click="handleClick" |
| :type="type" |
| > |
| <span v-if="loading" class="saap-button__spinner"> |
| <svg class="animate-spin -ml-1 mr-3 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> |
| <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> |
| <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> |
| </svg> |
| </span> |
| |
| <span v-if="icon && !loading" :class="iconClasses"> |
| <component :is="icon" /> |
| </span> |
| |
| <span class="saap-button__text"> |
| <slot>{{ label }}</slot> |
| </span> |
| </button> |
| </template> |
| |
| <script> |
| export default { |
| name: 'SaapButton', |
| props: { |
| variant: { |
| type: String, |
| default: 'primary', |
| validator: (value) => ['primary', 'secondary', 'tertiary', 'danger', 'success'].includes(value) |
| }, |
| size: { |
| type: String, |
| default: 'md', |
| validator: (value) => ['sm', 'md', 'lg', 'xl'].includes(value) |
| }, |
| disabled: { |
| type: Boolean, |
| default: false |
| }, |
| loading: { |
| type: Boolean, |
| default: false |
| }, |
| icon: { |
| type: [String, Object], |
| default: null |
| }, |
| iconPosition: { |
| type: String, |
| default: 'left', |
| validator: (value) => ['left', 'right'].includes(value) |
| }, |
| label: { |
| type: String, |
| default: '' |
| }, |
| type: { |
| type: String, |
| default: 'button', |
| validator: (value) => ['button', 'submit', 'reset'].includes(value) |
| }, |
| fullWidth: { |
| type: Boolean, |
| default: false |
| } |
| }, |
| emits: ['click'], |
| computed: { |
| buttonClasses() { |
| return [ |
| 'saap-button', |
| `saap-button--${this.variant}`, |
| `saap-button--${this.size}`, |
| { |
| 'saap-button--disabled': this.disabled, |
| 'saap-button--loading': this.loading, |
| 'saap-button--full-width': this.fullWidth, |
| 'saap-button--icon-right': this.icon && this.iconPosition === 'right' |
| } |
| ]; |
| }, |
| iconClasses() { |
| return [ |
| 'saap-button__icon', |
| { |
| 'saap-button__icon--right': this.iconPosition === 'right' |
| } |
| ]; |
| } |
| }, |
| methods: { |
| handleClick(event) { |
| if (!this.disabled && !this.loading) { |
| this.$emit('click', event); |
| } |
| } |
| } |
| }; |
| </script> |
| |
| <style scoped> |
| |
| .saap-button { |
| |
| @apply inline-flex items-center justify-center; |
| @apply font-medium rounded-lg transition-all duration-200; |
| @apply focus:outline-none focus:ring-2 focus:ring-offset-2; |
| @apply disabled:opacity-50 disabled:cursor-not-allowed; |
| |
| |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; |
| line-height: 1.5; |
| } |
| |
| |
| .saap-button--sm { |
| @apply px-3 py-1.5 text-sm; |
| min-height: 32px; |
| } |
| |
| .saap-button--md { |
| @apply px-4 py-2 text-sm; |
| min-height: 40px; |
| } |
| |
| .saap-button--lg { |
| @apply px-6 py-3 text-base; |
| min-height: 48px; |
| } |
| |
| .saap-button--xl { |
| @apply px-8 py-4 text-lg; |
| min-height: 56px; |
| } |
| |
| |
| .saap-button--primary { |
| background-color: #2563EB; |
| border: 1px solid #2563EB; |
| color: white; |
| } |
| |
| .saap-button--primary:hover:not(:disabled) { |
| background-color: #1D4ED8; |
| border-color: #1D4ED8; |
| box-shadow: 0 4px 6px -1px rgba(37, 99, 235, 0.1); |
| } |
| |
| .saap-button--primary:focus { |
| @apply ring-blue-500; |
| } |
| |
| .saap-button--secondary { |
| background-color: #16A34A; |
| border: 1px solid #16A34A; |
| color: white; |
| } |
| |
| .saap-button--secondary:hover:not(:disabled) { |
| background-color: #15803D; |
| border-color: #15803D; |
| box-shadow: 0 4px 6px -1px rgba(22, 163, 74, 0.1); |
| } |
| |
| .saap-button--secondary:focus { |
| @apply ring-green-500; |
| } |
| |
| .saap-button--tertiary { |
| background-color: transparent; |
| border: 1px solid #D1D5DB; |
| color: #374151; |
| } |
| |
| .saap-button--tertiary:hover:not(:disabled) { |
| background-color: #F9FAFB; |
| border-color: #9CA3AF; |
| } |
| |
| .saap-button--tertiary:focus { |
| @apply ring-gray-500; |
| } |
| |
| .saap-button--danger { |
| background-color: #DC2626; |
| border: 1px solid #DC2626; |
| color: white; |
| } |
| |
| .saap-button--danger:hover:not(:disabled) { |
| background-color: #B91C1C; |
| border-color: #B91C1C; |
| } |
| |
| .saap-button--danger:focus { |
| @apply ring-red-500; |
| } |
| |
| .saap-button--success { |
| background-color: #16A34A; |
| border: 1px solid #16A34A; |
| color: white; |
| } |
| |
| .saap-button--success:hover:not(:disabled) { |
| background-color: #15803D; |
| border-color: #15803D; |
| } |
| |
| |
| .saap-button--loading { |
| cursor: wait; |
| } |
| |
| .saap-button--full-width { |
| @apply w-full; |
| } |
| |
| |
| .saap-button__icon { |
| @apply w-5 h-5 flex-shrink-0; |
| } |
| |
| .saap-button__icon:not(.saap-button__icon--right) { |
| @apply mr-2; |
| } |
| |
| .saap-button__icon--right { |
| @apply ml-2 order-2; |
| } |
| |
| .saap-button--icon-right .saap-button__text { |
| @apply order-1; |
| } |
| |
| |
| .saap-button__spinner { |
| @apply flex items-center; |
| } |
| |
| |
| @media (prefers-reduced-motion: reduce) { |
| .saap-button { |
| transition: none; |
| } |
| } |
| |
| |
| @media (prefers-color-scheme: dark) { |
| .saap-button--tertiary { |
| background-color: transparent; |
| border-color: #4B5563; |
| color: #D1D5DB; |
| } |
| |
| .saap-button--tertiary:hover:not(:disabled) { |
| background-color: #111827; |
| border-color: #6B7280; |
| } |
| } |
| </style> |