feat: agregar menú dropdown de configuración compacto
- Crear componente SettingsMenu.client.vue con tres puntos verticales - Agrupar AuthIndicator y ThemeToggle en dropdown menu - Reducir espacio en barra principal para diseño más compacto - Agregar animaciones y transiciones suaves al dropdown - Cerrar menú automáticamente al hacer clic fuera o presionar Escape
This commit is contained in:
@@ -12,7 +12,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<AuthIndicator />
|
|
||||||
<SearchFilter
|
<SearchFilter
|
||||||
:results-count="filteredCount"
|
:results-count="filteredCount"
|
||||||
@search="$emit('search', $event)"
|
@search="$emit('search', $event)"
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
@shuffle-changed="$emit('shuffle-changed', $event)"
|
@shuffle-changed="$emit('shuffle-changed', $event)"
|
||||||
@repeat-changed="$emit('repeat-changed', $event)"
|
@repeat-changed="$emit('repeat-changed', $event)"
|
||||||
/>
|
/>
|
||||||
<ThemeToggle />
|
<SettingsMenu />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -39,10 +38,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Music } from 'lucide-vue-next'
|
import { Music } from 'lucide-vue-next'
|
||||||
import AuthIndicator from './AuthIndicator.client.vue'
|
|
||||||
import ThemeToggle from './ThemeToggle.client.vue'
|
|
||||||
import PlaybackControls from './PlaybackControls.client.vue'
|
import PlaybackControls from './PlaybackControls.client.vue'
|
||||||
import SearchFilter from './SearchFilter.client.vue'
|
import SearchFilter from './SearchFilter.client.vue'
|
||||||
|
import SettingsMenu from './SettingsMenu.client.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentTrack: {
|
currentTrack: {
|
||||||
|
|||||||
197
components/SettingsMenu.client.vue
Normal file
197
components/SettingsMenu.client.vue
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<div class="settings-menu" ref="menuContainer">
|
||||||
|
<IconButton
|
||||||
|
:icon="MoreVertical"
|
||||||
|
title="Configuración"
|
||||||
|
size="small"
|
||||||
|
@click="toggleMenu"
|
||||||
|
class="menu-trigger"
|
||||||
|
:active="isOpen"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<transition name="dropdown">
|
||||||
|
<div v-if="isOpen" class="dropdown-menu glass">
|
||||||
|
<div class="menu-section">
|
||||||
|
<div class="menu-label">Autenticación</div>
|
||||||
|
<AuthIndicator class="menu-item-auth" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-divider"></div>
|
||||||
|
|
||||||
|
<div class="menu-section">
|
||||||
|
<div class="menu-label">Tema</div>
|
||||||
|
<div class="menu-item">
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { MoreVertical } from 'lucide-vue-next'
|
||||||
|
import IconButton from './IconButton.client.vue'
|
||||||
|
import AuthIndicator from './AuthIndicator.client.vue'
|
||||||
|
import ThemeToggle from './ThemeToggle.client.vue'
|
||||||
|
|
||||||
|
const isOpen = ref(false)
|
||||||
|
const menuContainer = ref(null)
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
isOpen.value = !isOpen.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeMenu = () => {
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerrar el menú al hacer clic fuera
|
||||||
|
const handleClickOutside = (event) => {
|
||||||
|
if (menuContainer.value && !menuContainer.value.contains(event.target)) {
|
||||||
|
closeMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerrar el menú al presionar Escape
|
||||||
|
const handleEscape = (event) => {
|
||||||
|
if (event.key === 'Escape' && isOpen.value) {
|
||||||
|
closeMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
document.addEventListener('keydown', handleEscape)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
document.removeEventListener('keydown', handleEscape)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.settings-menu {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-trigger:hover {
|
||||||
|
animation: pulse-menu 0.6s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
min-width: 220px;
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: var(--shadow-glass), 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 0 8px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-auth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajustar el AuthIndicator para que se vea bien en el menú */
|
||||||
|
.menu-item-auth :deep(.auth-indicator) {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.2),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
.dropdown-enter-active,
|
||||||
|
.dropdown-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transform-origin: top right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-menu {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dropdown-menu {
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.dropdown-menu {
|
||||||
|
/* En móvil, centrar mejor el menú */
|
||||||
|
right: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user