As a beginner in Vue.js, I am currently working on a CRUD project for my coursework.
One issue I am facing is with the behavior of the dropdown menu. Can anyone provide assistance?
https://i.sstatic.net/1MozuN3L.png
I have specific requirements:
- The dropdown menu without an active state should automatically close when a user tries to open another dropdown menu without an active state.
- A dropdown menu with an active state should not close automatically; only the user can close or open it.
- If a user closes a dropdown menu that has an active state, I want the dropdown menu without an active state to remain open if it was already open.
Note:
An active state is triggered when a user accesses :active route.
isOpen represents the value indicating whether the dropdown menu is open or closed (collapsed or not collapsed).
sidebarContent.vue contains all the menu items on the sidebar.
sidebarCollapsibleItem.vue handles the links for the dropdown menus.
sidebarCollapsible.vue acts as a container for sidebarCollapsibleItem to create dropdown menus.
sidebarLink.vue behaves like an anchor tag in HTML to manage non-dropdown menu items.
How can I achieve these requirements?
Below is a snippet of my code:
sidebarCollapsible.vue
<script setup>
import { ref, watch } from 'vue';
import { sidebarState } from '@/Composables';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { EmptyCircleIcon } from '@/Components/Icons/Outline';
const props = defineProps({
title: {
type: String,
},
icon: {
required: false,
},
active: {
type: Boolean,
default: false,
},
});
const { active } = props;
const isOpen = ref(active);
const beforeEnter = (el) => {
el.style.maxHeight = `0px`;
};
const enter = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const beforeLeave = (el) => {
el.style.maxHeight = `${el.scrollHeight}px`;
};
const leave = (el) => {
el.style.maxHeight = `0px`;
};
watch(() => sidebarState.isHovered, (sideHover) => {
if (!sideHover && !sidebarState.isOpen && !active) {
isOpen.value = false;
};
});
</script>
<template>
<div class="relative">
<SidebarLink @click="isOpen = !isOpen" :title="title" :active="active">
<template #icon>
<slot name="icon">
<EmptyCircleIcon aria-hidden="true" class="flex-shrink-0 w-6 h-6" />
</slot>
</template>
<template #arrow>
<span v-show="sidebarState.isOpen || sidebarState.isHovered" aria-hidden="true" class="relative block w-6 h-6 ml-auto">
<span :class="['absolute right-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'-rotate-45': isOpen, 'rotate-45': !isOpen}]"></span>
<span :class="['absolute left-[9px] bg-gray-400 mt-[-5px] h-2 w-[2px] top-1/2 transition-all duration-200', {'rotate-45': isOpen, '-rotate-45': !isOpen}]"></span>
</span>
</template>
</SidebarLink>
<transition
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave"
appear>
<div v-show="isOpen && (sidebarState.isOpen || sidebarState.isHovered)" class="overflow-hidden transition-all duration-200">
<ul class="relative px-0 pt-2 pb-0 ml-5 before:w-0 before:block before:absolute before:inset-y-0 before:left-0 before:border-l-2 before:border-l-gray-200 dark:before:border-l-gray-600">
<slot />
</ul>
</div>
</transition>
</div>
</template>
sidebarCollapsibleItem.vue
<script setup>
import { defineProps } from 'vue';
import { Link } from '@inertiajs/vue3';
const props = defineProps({
href: String,
title: String,
active: {
type: Boolean,
default: false,
},
external: {
type: Boolean,
default: false,
},
});
const { external } = props;
const Tag = external ? 'a' : Link;
</script>
<template>
<li
:class="[
'relative leading-8 m-0 pl-6',
'before:block before:w-4 before:h-0 before:absolute before:left-0 before:top-4 before:border-t-2 before:border-t-gray-200 before:-mt-0.5',
'last:before:bg-white last:before:h-auto last:before:top-4 last:before:bottom-0',
'dark:last:before:bg-dark-eval-1 dark:before:border-t-gray-600',
]"
>
<component
:is="Tag"
:href="href"
v-bind="$attrs"
:class="[
'transition-colors hover:text-gray-900 dark:hover:text-gray-100',
{
'text-purple-500 dark:text-purple-500 hover:text-purple-600 dark:hover:text-purple-600': active,
'text-gray-500 dark:text-gray-400': !active,
},
]"
>
{{ title }}
</component>
</li>
</template>
sidebarContent.vue
<script setup>
import PerfrectScrollbar from '@/Components/PerfectScrollbar';
import SidebarLink from '@/Components/Sidebar/SidebarLink.vue';
import { DashboardIcon } from '@/Components/Icons/Outline';
import { UserGroupIcon, BuildingOffice2Icon } from '@heroicons/vue/24/outline';
import SidebarCollapsible from '@/Components/Sidebar/SidebarCollapsible.vue';
import SidebarCollapsibleItem from '@/Components/Sidebar/SidebarCollapsibleItem.vue';
</script>
<template>
<PerfrectScrollbar
tagname="nav"
aria-label="main"
class="relative flex flex-col flex-1 max-h-full gap-4 px-3"
>
<SidebarLink
title="Dashboard"
:href="route('dashboard')"
:active="route().current('dashboard')"
>
<template #icon>
<DashboardIcon
class="flex-shrink-0 w-6 h-6"
aria-hidden="true"
/>
</template>
</SidebarLink>
<SidebarCollapsible
title="Management"
:active="route().current('users.*') || route().current('teams.*') || route().current('divisions.*') || route().current('departments.*')"
>
<template #icon>
<UserGroupIcon
class="flex-shrink-0 w-6 h-6"
aria-hidden="true"
/>
</template>
<SidebarCollapsibleItem
title="Users"
:href="route('users.index')"
:active="route().current('users.*')"
/>
<SidebarCollapsibleItem
title="Roles"
:href="route('teams.index')"
:active="route().current('#')"
/>
<SidebarCollapsibleItem
:href="route('teams.index')"
title="Teams"
:active="route().current('teams.*')"
/>
<SidebarCollapsibleItem
title="Departments"
:href="route('departments.index')"
:active="route().current('departments.*')"
/>
<SidebarCollapsibleItem
title="Divisions"
:href="route('divisions.index')"
:active="route().current('divisions.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
title="Locations"
:active="route().current('externals.*') || route().current('internals.*')"
>
<template #icon>
<BuildingOffice2Icon
class="flex-shrink-0 w-6 h-6"
aria-hidden="true"
/>
</template>
<SidebarCollapsibleItem
title="Internals"
:href="route('internals.index')"
:active="route().current('internals.*')"
/>
<SidebarCollapsibleItem
title="Externals"
:href="route('externals.index')"
:active="route().current('externals.*')"
/>
</SidebarCollapsible>
<SidebarCollapsible
title="test"
:active="route().current('api-tokens.*')"
>
<template #icon>
<BuildingOffice2Icon
class="flex-shrink-0 w-6 h-6"
aria-hidden="true"
/>
</template>
<SidebarCollapsibleItem
title="Internals"
:href="route('api-tokens.index')"
:active="route().current('user.*')"
/>
<SidebarCollapsibleItem
title="Externals"
:href="route('api-tokens.index')"
:active="route().current('api-tokens.*')"
/>
</SidebarCollapsible>
</PerfrectScrollbar>
</template>
Thanks for any assistance provided. Apologies for any language barriers due to my limited proficiency in English.