147 lines
2.7 KiB
Vue
147 lines
2.7 KiB
Vue
<template>
|
|
<BaseButton
|
|
variant="icon"
|
|
:active="active"
|
|
:disabled="disabled"
|
|
:title="title"
|
|
@click="$emit('click', $event)"
|
|
:class="[
|
|
'icon-button',
|
|
{
|
|
'xsmall': size === 'xsmall',
|
|
'large': size === 'large',
|
|
'small': size === 'small'
|
|
}
|
|
]"
|
|
>
|
|
<component v-if="typeof icon === 'object' || typeof icon === 'function'" :is="icon" class="icon" :size="iconSize" />
|
|
<span v-else class="icon">{{ icon }}</span>
|
|
<div v-if="badge" class="badge">{{ badge }}</div>
|
|
</BaseButton>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import BaseButton from './BaseButton.client.vue'
|
|
|
|
const props = defineProps({
|
|
icon: {
|
|
type: [String, Object, Function],
|
|
required: true
|
|
},
|
|
active: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
size: {
|
|
type: String,
|
|
default: 'normal',
|
|
validator: (value) => ['xsmall', 'small', 'normal', 'large'].includes(value)
|
|
},
|
|
badge: {
|
|
type: [String, Number],
|
|
default: null
|
|
}
|
|
})
|
|
|
|
defineEmits(['click'])
|
|
|
|
const iconSize = computed(() => {
|
|
switch (props.size) {
|
|
case 'xsmall': return 16
|
|
case 'small': return 18
|
|
case 'large': return 28
|
|
default: return 20
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.icon-button {
|
|
position: relative;
|
|
}
|
|
|
|
.icon-button.xsmall {
|
|
width: 28px;
|
|
height: 28px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.icon-button.small {
|
|
width: 36px;
|
|
height: 36px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.icon-button.large {
|
|
width: 60px;
|
|
height: 60px;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.icon {
|
|
display: block;
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.icon-button:hover .icon {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.badge {
|
|
position: absolute;
|
|
top: -4px;
|
|
right: -4px;
|
|
background: var(--accent-primary);
|
|
color: white;
|
|
border-radius: 50%;
|
|
width: 18px;
|
|
height: 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Special animations for specific icons */
|
|
.icon-button[title*="Shuffle"]:hover .icon {
|
|
animation: shuffle 0.5s ease-in-out;
|
|
}
|
|
|
|
.icon-button[title*="Repeat"]:hover .icon {
|
|
animation: rotate 0.5s ease-in-out;
|
|
}
|
|
|
|
.icon-button[title*="Play"]:hover .icon,
|
|
.icon-button[title*="Pause"]:hover .icon {
|
|
animation: pulse-icon 0.3s ease-in-out;
|
|
}
|
|
|
|
@keyframes shuffle {
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-2px); }
|
|
75% { transform: translateX(2px); }
|
|
}
|
|
|
|
@keyframes rotate {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
@keyframes pulse-icon {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.2); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
</style>
|