Fix: Usar DropdownMenu de radix-vue para menú de configuración
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 26s

- Reemplaza dropdown manual por componente DropdownMenu con radix-vue
- Agrega avoid-collisions y collision-padding para posicionamiento automático
- El menú ahora se ajusta automáticamente cuando no hay espacio horizontal
- Actualiza DropdownMenuItem para soportar prop 'as' y 'href' para enlaces
This commit is contained in:
2025-11-25 01:17:39 -06:00
parent 2d1c2b1df3
commit 6639c93cfe
3 changed files with 41 additions and 25 deletions

View File

@@ -1,6 +1,6 @@
<script setup>
import { onMounted, reactive, ref, computed, watch } from 'vue';
import { Button, Badge, Input, Card, CardHeader, CardTitle, CardActions, CardContent, Textarea } from '@/components/ui';
import { Button, Badge, Input, Card, CardHeader, CardTitle, CardActions, CardContent, Textarea, DropdownMenu, DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui';
import EventCard from '@/components/EventCard.vue';
import UserCard from '@/components/UserCard.vue';
import DispositivoCard from '@/components/DispositivoCard.vue';
@@ -45,7 +45,6 @@ const mainCollapsed = ref(false);
const layoutMode = ref('user');
const theme = ref(localStorage.getItem('theme') || 'dark');
const statusText = ref('OK');
const showSettingsMenu = ref(false);
const showInfoModal = ref(false);
async function fetchUsers() {
@@ -305,13 +304,12 @@ function openAddGuest() {
userFormModel.value = { username:'', password:'', vlan:'5', disabled:false, etiquetas: ['invitado'] };
showUserForm.value = true;
}
function toggleSettingsMenu() { showSettingsMenu.value = !showSettingsMenu.value; }
const showRawDb = ref(false);
const rawDbFullscreen = ref(false);
function openRawDb() { showSettingsMenu.value = false; showRawDb.value = true; }
function openRawDb() { showRawDb.value = true; }
function closeRawDb() { showRawDb.value = false; }
const showVlan = ref(false);
function openVlanForm() { showSettingsMenu.value = false; showVlan.value = true; }
function openVlanForm() { showVlan.value = true; }
function closeVlan() { showVlan.value = false; }
function onVlanCreated() { showVlan.value = false; }
const showDevice = ref(false);
@@ -426,22 +424,23 @@ async function handleUserFormSubmit(data) {
<img class="size-4 opacity-90" src="/icons/guest.svg" alt="invitado"> Invitado
</Button>
<UserDropdown />
<div class="relative">
<Button @click="toggleSettingsMenu">
<img class="size-4 opacity-90" src="/icons/settings.svg" alt="config"> Configuración
</Button>
<div v-if="showSettingsMenu" class="absolute right-0 top-full mt-1.5 glass-card p-1.5 min-w-[200px] shadow-lg border border-pink-200 dark:border-pink-600/50 z-50 space-y-1">
<Button variant="ghost" class="w-full justify-start" @click="openRawDb">ver rawDB</Button>
<Button variant="ghost" class="w-full justify-start" @click="openVlanForm">crear VLAN</Button>
<hr class="border-border my-1" />
<Button as="a" variant="ghost" class="w-full justify-start" href="/api/users.csv">Exportar usuarios CSV</Button>
<Button as="a" variant="ghost" class="w-full justify-start" href="/api/devices.csv">Exportar dispositivos CSV</Button>
<Button as="a" variant="ghost" class="w-full justify-start" href="/api/vlans.csv">Exportar VLANs CSV</Button>
<Button variant="ghost" class="w-full justify-start" @click="openImport('users')">Importar usuarios CSV</Button>
<Button variant="ghost" class="w-full justify-start" @click="openImport('devices')">Importar dispositivos CSV</Button>
<Button variant="ghost" class="w-full justify-start" @click="openImport('vlans')">Importar VLANs CSV</Button>
</div>
</div>
<DropdownMenu align="end">
<template #trigger>
<Button>
<img class="size-4 opacity-90" src="/icons/settings.svg" alt="config"> Configuración
</Button>
</template>
<DropdownMenuItem @click="openRawDb">ver rawDB</DropdownMenuItem>
<DropdownMenuItem @click="openVlanForm">crear VLAN</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem as="a" href="/api/users.csv">Exportar usuarios CSV</DropdownMenuItem>
<DropdownMenuItem as="a" href="/api/devices.csv">Exportar dispositivos CSV</DropdownMenuItem>
<DropdownMenuItem as="a" href="/api/vlans.csv">Exportar VLANs CSV</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem @click="openImport('users')">Importar usuarios CSV</DropdownMenuItem>
<DropdownMenuItem @click="openImport('devices')">Importar dispositivos CSV</DropdownMenuItem>
<DropdownMenuItem @click="openImport('vlans')">Importar VLANs CSV</DropdownMenuItem>
</DropdownMenu>
</div>
</header>

View File

@@ -6,7 +6,7 @@ import {
DropdownMenuContent
} from 'radix-vue';
const props = defineProps({
defineProps({
align: {
type: String,
default: 'end'
@@ -14,6 +14,14 @@ const props = defineProps({
side: {
type: String,
default: 'bottom'
},
sideOffset: {
type: Number,
default: 6
},
collisionPadding: {
type: [Number, Object],
default: 8
}
});
</script>
@@ -28,7 +36,9 @@ const props = defineProps({
<DropdownMenuContent
:align="align"
:side="side"
:side-offset="6"
:side-offset="sideOffset"
:collision-padding="collisionPadding"
:avoid-collisions="true"
class="z-50 min-w-[180px] glass-card p-1.5 shadow-lg border border-pink-200 dark:border-pink-600/50 animate-slide-in"
>
<slot />

View File

@@ -6,7 +6,12 @@ import { cn } from '@/lib/utils';
const props = defineProps({
disabled: Boolean,
danger: Boolean,
class: String
class: String,
as: {
type: [String, Object],
default: undefined
},
href: String
});
const classes = computed(() => cn(
@@ -17,10 +22,12 @@ const classes = computed(() => cn(
props.danger && 'hover:border-red-400/30 hover:text-red-400',
props.class
));
const elementAs = computed(() => props.as || (props.href ? 'a' : undefined));
</script>
<template>
<DropdownMenuItem :disabled="disabled" :class="classes">
<DropdownMenuItem :as="elementAs" :href="href" :disabled="disabled" :class="classes">
<slot />
</DropdownMenuItem>
</template>