127 lines
3.4 KiB
Vue
127 lines
3.4 KiB
Vue
<template>
|
|
<div :class="containerClasses">
|
|
<button
|
|
v-for="(option, index) in options"
|
|
:key="option.value"
|
|
:type="type"
|
|
:disabled="disabled"
|
|
:class="getButtonClasses(option.value, index)"
|
|
@click="handleSelect(option.value)"
|
|
>
|
|
<!-- Icon (optional) -->
|
|
<span v-if="option.icon" class="mr-2" v-html="option.icon"/>
|
|
|
|
<!-- Label -->
|
|
<span>{{ option.label }}</span>
|
|
|
|
<!-- Badge (optional) -->
|
|
<span
|
|
v-if="option.badge"
|
|
class="ml-2 px-1.5 py-0.5 text-xs rounded-full bg-white/20"
|
|
>
|
|
{{ option.badge }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
export interface ButtonGroupOption {
|
|
value: string
|
|
label: string
|
|
icon?: string // SVG or emoji
|
|
badge?: string | number
|
|
}
|
|
|
|
interface Props {
|
|
options: ButtonGroupOption[]
|
|
modelValue: string
|
|
disabled?: boolean
|
|
type?: 'button' | 'submit' | 'reset'
|
|
size?: 'sm' | 'md' | 'lg'
|
|
variant?: 'primary' | 'secondary'
|
|
vertical?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
disabled: false,
|
|
type: 'button',
|
|
size: 'md',
|
|
variant: 'primary',
|
|
vertical: false,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
}>()
|
|
|
|
const containerClasses = computed(() => {
|
|
const base = 'inline-flex gap-0'
|
|
const direction = props.vertical ? 'flex-col w-full' : 'flex-row flex-wrap'
|
|
return `${base} ${direction}`
|
|
})
|
|
|
|
const getButtonClasses = (value: string, index: number) => {
|
|
const isSelected = value === props.modelValue
|
|
const isFirst = index === 0
|
|
const isLast = index === props.options.length - 1
|
|
|
|
// Base classes
|
|
const base = 'relative inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed border'
|
|
|
|
// Size classes
|
|
const sizeClasses = {
|
|
sm: 'px-3 py-1.5 text-sm min-h-[36px]',
|
|
md: 'px-4 py-2.5 text-base min-h-[44px]',
|
|
lg: 'px-5 py-3 text-lg min-h-[52px]',
|
|
}
|
|
|
|
// Variant classes (selected vs unselected)
|
|
let variantClasses = ''
|
|
if (props.variant === 'primary') {
|
|
variantClasses = isSelected
|
|
? 'bg-gradient-to-r from-primary to-blue-600 text-white border-blue-600 shadow-lg z-10'
|
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
|
} else {
|
|
variantClasses = isSelected
|
|
? 'bg-gradient-to-r from-gray-600 to-gray-700 text-white border-gray-600 shadow-lg z-10'
|
|
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
|
}
|
|
|
|
// Border radius (first and last buttons)
|
|
let roundedClasses = ''
|
|
if (props.vertical) {
|
|
if (isFirst) roundedClasses = 'rounded-t-lg'
|
|
if (isLast) roundedClasses = 'rounded-b-lg'
|
|
if (!isFirst && !isLast) roundedClasses = ''
|
|
} else {
|
|
if (isFirst) roundedClasses = 'rounded-l-lg'
|
|
if (isLast) roundedClasses = 'rounded-r-lg'
|
|
if (!isFirst && !isLast) roundedClasses = ''
|
|
}
|
|
|
|
// Border handling (remove double borders)
|
|
const borderClasses = !isFirst && !props.vertical ? '-ml-px' : ''
|
|
|
|
// Width for vertical layout
|
|
const widthClass = props.vertical ? 'w-full' : ''
|
|
|
|
return [
|
|
base,
|
|
sizeClasses[props.size],
|
|
variantClasses,
|
|
roundedClasses,
|
|
borderClasses,
|
|
widthClass,
|
|
].join(' ')
|
|
}
|
|
|
|
const handleSelect = (value: string) => {
|
|
if (!props.disabled) {
|
|
emit('update:modelValue', value)
|
|
}
|
|
}
|
|
</script>
|