|
| 1 | +<template> |
| 2 | + <NuxtLink |
| 3 | + v-if="isNuxtLink" |
| 4 | + :to="href" |
| 5 | + :class="buttonClasses" |
| 6 | + > |
| 7 | + <div v-if="variant === 'primary'" class="absolute inset-0 bg-gradient-to-r from-blue-600 to-cyan-700 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> |
| 8 | + <component v-if="icon" :is="icon" class="relative z-10 w-5 h-5 group-hover:rotate-12 transition-transform duration-300" /> |
| 9 | + <span class="relative z-10"><slot /></span> |
| 10 | + <ArrowTopRightOnSquareIcon v-if="showExternalIcon && !isNuxtLink" class="relative z-10 w-4 h-4 ml-1" /> |
| 11 | + </NuxtLink> |
| 12 | + <a |
| 13 | + v-else |
| 14 | + :href="href" |
| 15 | + :target="external ? '_blank' : undefined" |
| 16 | + :rel="external ? 'noopener noreferrer' : undefined" |
| 17 | + :class="buttonClasses" |
| 18 | + > |
| 19 | + <div v-if="variant === 'primary'" class="absolute inset-0 bg-gradient-to-r from-blue-600 to-cyan-700 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div> |
| 20 | + <component v-if="icon" :is="icon" class="relative z-10 w-5 h-5 group-hover:rotate-12 transition-transform duration-300" /> |
| 21 | + <span class="relative z-10"><slot /></span> |
| 22 | + </a> |
| 23 | +</template> |
| 24 | + |
| 25 | +<script setup lang="ts"> |
| 26 | +import type { Component } from 'vue' |
| 27 | +import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline' |
| 28 | +
|
| 29 | +type ButtonVariant = 'primary' | 'secondary' |
| 30 | +type ButtonSize = 'sm' | 'md' | 'lg' |
| 31 | +
|
| 32 | +interface Props { |
| 33 | + variant?: ButtonVariant |
| 34 | + size?: ButtonSize |
| 35 | + href: string |
| 36 | + icon?: Component |
| 37 | + isNuxtLink?: boolean |
| 38 | + external?: boolean |
| 39 | + showExternalIcon?: boolean |
| 40 | +} |
| 41 | +
|
| 42 | +const props = withDefaults(defineProps<Props>(), { |
| 43 | + variant: 'primary', |
| 44 | + size: 'lg', |
| 45 | + isNuxtLink: false, |
| 46 | + external: true, |
| 47 | + showExternalIcon: false |
| 48 | +}) |
| 49 | +
|
| 50 | +const SIZE_CLASSES: Record<ButtonSize, string> = { |
| 51 | + sm: 'px-4 py-2 text-sm min-h-[40px]', |
| 52 | + md: 'px-5 py-3 text-base min-h-[48px]', |
| 53 | + lg: 'px-6 sm:px-8 py-4 text-base sm:text-lg min-h-[56px]' |
| 54 | +} |
| 55 | +
|
| 56 | +const BASE_CLASSES = 'cursor-pointer group relative inline-flex items-center justify-center gap-3 font-semibold rounded-full overflow-hidden transition-all duration-300 hover:scale-105' |
| 57 | +
|
| 58 | +const VARIANT_STYLES: Record<ButtonVariant, string> = { |
| 59 | + primary: 'text-white bg-gradient-to-r from-blue-500 to-cyan-600 hover:shadow-2xl hover:shadow-blue-500/25', |
| 60 | + secondary: 'text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-white/5 border border-gray-300 dark:border-white/10 backdrop-blur-sm hover:bg-gray-200 dark:hover:bg-white/10 hover:border-gray-400 dark:hover:border-white/20' |
| 61 | +} |
| 62 | +
|
| 63 | +const buttonClasses = computed(() => `${BASE_CLASSES} ${SIZE_CLASSES[props.size]} ${VARIANT_STYLES[props.variant]}`) |
| 64 | +</script> |
0 commit comments