Sidebar is closed when links are clicked on mobile
All checks were successful
Production Build and Deploy / Build (push) Successful in 1m6s
Production Build and Deploy / Deploy (push) Successful in 22s

This commit is contained in:
Liviu Burcusel 2026-01-20 12:42:09 +01:00
parent 84252b76bb
commit 3d1627a384
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
5 changed files with 71 additions and 14 deletions

View file

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { computed } from "vue";
import { useSidebar } from "~/components/ui/sidebar";
import { useRuntimeConfig } from "#app"; import { useRuntimeConfig } from "#app";
import { BookOpen, BookOpenText, ChevronRight, HandCoins, Settings2, SquareTerminal } from "lucide-vue-next"; import { BookOpen, BookOpenText, ChevronRight, HandCoins, Settings2, SquareTerminal } from "lucide-vue-next";
@ -99,6 +100,14 @@ const props = withDefaults(defineProps<SidebarLayoutProps>(), {
const navMain = computed(() => props.navItems || data.navMain); const navMain = computed(() => props.navItems || data.navMain);
const config = useRuntimeConfig(); const config = useRuntimeConfig();
const { isMobile, setOpenMobile } = useSidebar();
const closeSidebarOnMobile = () => {
if (isMobile.value) {
setOpenMobile(false);
}
};
</script> </script>
<template> <template>
@ -107,7 +116,7 @@ const config = useRuntimeConfig();
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton size="lg" as-child> <SidebarMenuButton size="lg" as-child>
<NuxtLink to="/"> <NuxtLink to="/" @click="closeSidebarOnMobile">
<div <div
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground" class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
> >
@ -147,7 +156,7 @@ const config = useRuntimeConfig();
<SidebarMenuSub> <SidebarMenuSub>
<SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title"> <SidebarMenuSubItem v-for="subItem in item.items" :key="subItem.title">
<SidebarMenuSubButton as-child> <SidebarMenuSubButton as-child>
<NuxtLink :to="subItem.url"> <NuxtLink :to="subItem.url" @click="closeSidebarOnMobile">
<span>{{ subItem.title }}</span> <span>{{ subItem.title }}</span>
</NuxtLink> </NuxtLink>
</SidebarMenuSubButton> </SidebarMenuSubButton>

View file

@ -16,7 +16,7 @@ import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogIn, LogOut } from "luc
import type { User } from "better-auth"; import type { User } from "better-auth";
const { isMobile } = useSidebar(); const { isMobile, setOpenMobile } = useSidebar();
const props = defineProps<{ user?: User | null | undefined }>(); const props = defineProps<{ user?: User | null | undefined }>();
@ -28,12 +28,9 @@ const userInititials = computed(() => {
.join(""); .join("");
}); });
const handleLogout = () => { const handleAuthNavigation = (action: string) => {
navigateTo("/auth/logout"); setOpenMobile(false);
}; navigateTo(`/auth/${action}`);
const handleLogin = () => {
navigateTo("/auth/login");
}; };
</script> </script>
@ -93,7 +90,7 @@ const handleLogin = () => {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem @click="handleLogout"> <DropdownMenuItem @click="handleAuthNavigation('logout')">
<LogOut /> <LogOut />
Log out Log out
</DropdownMenuItem> </DropdownMenuItem>
@ -136,7 +133,7 @@ const handleLogin = () => {
</div> </div>
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem @click="handleLogin"> <DropdownMenuItem @click="handleAuthNavigation('login')">
<LogIn /> <LogIn />
Log in Log in
</DropdownMenuItem> </DropdownMenuItem>

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.5", "version": "0.0.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.5", "version": "0.0.6",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@pinia/nuxt": "^0.11.3", "@pinia/nuxt": "^0.11.3",

View file

@ -1,6 +1,6 @@
{ {
"name": "glowing-fiesta", "name": "glowing-fiesta",
"version": "0.0.5", "version": "0.0.6",
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "scripts": {

View file

@ -267,4 +267,55 @@ describe("SidebarLayout", () => {
// and renders the menu items. // and renders the menu items.
expect(text).toContain("Playground"); expect(text).toContain("Playground");
}); });
it("calls setOpenMobile(false) when clicking a sub-menu item on mobile", async () => {
const setOpenMobile = vi.fn();
useSidebarMock.mockReturnValue({
isMobile: ref(true),
state: ref("expanded"),
openMobile: ref(true),
setOpenMobile,
});
const wrapper = mount({
components: { SidebarLayout },
template: "<Suspense><SidebarLayout /></Suspense>",
});
await flushPromises();
// Find the link containing 'History' (from default data)
const link = wrapper.findAll("a").find((el) => el.text().includes("History"));
expect(link).toBeDefined();
await link.trigger("click");
expect(setOpenMobile).toHaveBeenCalledTimes(1);
expect(setOpenMobile).toHaveBeenCalledWith(false);
});
it("does not call setOpenMobile(false) when clicking a sub-menu item on desktop", async () => {
const setOpenMobile = vi.fn();
useSidebarMock.mockReturnValue({
isMobile: ref(false),
state: ref("expanded"),
openMobile: ref(true),
setOpenMobile,
});
const wrapper = mount({
components: { SidebarLayout },
template: "<Suspense><SidebarLayout /></Suspense>",
});
await flushPromises();
// Find the link containing 'History' (from default data)
const link = wrapper.findAll("a").find((el) => el.text().includes("History"));
expect(link).toBeDefined();
await link.trigger("click");
expect(setOpenMobile).not.toHaveBeenCalled();
});
}); });