Răsfoiți Sursa

Fix mobile sidebar: auto-close on navigation and improve settings layout (#36)

* better navigation for mobile

* settings sidebar
Bernt Christian Egeland 8 luni în urmă
părinte
comite
7656e1a087

+ 7 - 2
src/app/(authenticated)/settings/layout.tsx

@@ -57,10 +57,15 @@ export default async function SettingsLayout({
             </p>
           </div>
           <div className="flex min-h-0 flex-1 flex-col gap-6 md:flex-row">
-            <aside className="w-full shrink-0 overflow-y-auto md:w-56 lg:w-64">
+            <aside className="hidden shrink-0 overflow-y-auto md:block md:w-56 lg:w-64">
               <SettingsNav features={features} isCloud={isCloudMode()} />
             </aside>
-            <div className="min-w-0 flex-1 overflow-y-auto pb-8">{children}</div>
+            <div className="min-w-0 flex-1 overflow-y-auto pb-8">
+              <div className="mb-4 md:hidden">
+                <SettingsNav features={features} isCloud={isCloudMode()} mobile />
+              </div>
+              {children}
+            </div>
           </div>
         </div>
       </div>

+ 34 - 2
src/app/(authenticated)/settings/settings-nav.tsx

@@ -1,10 +1,17 @@
 "use client";
 
 import Link from "next/link";
-import { usePathname } from "next/navigation";
+import { usePathname, useRouter } from "next/navigation";
 import { useTranslations } from "next-intl";
 import { cn } from "@/lib/utils";
 import type { PlanFeatures } from "@/lib/features";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
 import {
   Banknote,
   Building2,
@@ -86,8 +93,9 @@ const settingsCategories: SettingsCategory[] = [
   },
 ];
 
-export function SettingsNav({ features, isCloud }: { features?: PlanFeatures; isCloud?: boolean }) {
+export function SettingsNav({ features, isCloud, mobile }: { features?: PlanFeatures; isCloud?: boolean; mobile?: boolean }) {
   const pathname = usePathname();
+  const router = useRouter();
   const t = useTranslations("settings");
 
   const filterItem = (item: SettingsNavItem) => {
@@ -96,6 +104,30 @@ export function SettingsNav({ features, isCloud }: { features?: PlanFeatures; is
     return true;
   };
 
+  if (mobile) {
+    const allItems = settingsCategories.flatMap((c) => c.items.filter(filterItem));
+    const activeItem = allItems.find((item) => pathname === item.href);
+
+    return (
+      <Select value={activeItem?.href ?? pathname} onValueChange={(value) => router.push(value)}>
+        <SelectTrigger className="w-full">
+          <SelectValue placeholder={t("title")} />
+        </SelectTrigger>
+        <SelectContent>
+          {settingsCategories.map((category) => {
+            const visibleItems = category.items.filter(filterItem);
+            if (visibleItems.length === 0) return null;
+            return visibleItems.map((item) => (
+              <SelectItem key={item.href} value={item.href}>
+                {t(`nav.items.${item.key}.title`)}
+              </SelectItem>
+            ));
+          })}
+        </SelectContent>
+      </Select>
+    );
+  }
+
   return (
     <nav className="flex flex-col gap-4">
       {settingsCategories.map((category) => {

+ 10 - 4
src/components/app-sidebar.tsx

@@ -31,6 +31,7 @@ import {
   SidebarMenu,
   SidebarMenuButton,
   SidebarMenuItem,
+  useSidebar,
 } from '@/components/ui/sidebar'
 import { Input } from '@/components/ui/input'
 import { Label } from '@/components/ui/label'
@@ -96,6 +97,7 @@ export function AppSidebar({
   const router = useRouter()
   const { data: session } = useSession()
   const { theme, setTheme } = useTheme()
+  const { setOpenMobile, isMobile } = useSidebar()
   const currentLocale = useLocale()
   const t = useTranslations('navigation')
   const [showCreateOrg, setShowCreateOrg] = React.useState(false)
@@ -124,13 +126,17 @@ export function AppSidebar({
     ...(showReports ? [{ titleKey: 'sidebar.reports' as const, url: '/reports', icon: BarChart3 }] : []),
   ]
 
+  const closeMobileSidebar = () => {
+    if (isMobile) setOpenMobile(false)
+  }
+
   const renderNavGroup = (items: { titleKey: string; url: string; icon: React.ComponentType<{ className?: string }> }[]) =>
     items.map((item) => {
       const isActive = pathname === item.url || (item.url !== '/' && pathname.startsWith(item.url))
       return (
         <SidebarMenuItem key={item.titleKey}>
           <SidebarMenuButton asChild isActive={isActive}>
-            <Link href={item.url} className="font-medium">
+            <Link href={item.url} className="font-medium" onClick={closeMobileSidebar}>
               <item.icon className="size-4" />
               {t(item.titleKey)}
             </Link>
@@ -242,7 +248,7 @@ export function AppSidebar({
           <SidebarMenu className="gap-2">
             <SidebarMenuItem>
               <SidebarMenuButton asChild isActive={dashboardActive}>
-                <Link href="/" className="font-medium">
+                <Link href="/" className="font-medium" onClick={closeMobileSidebar}>
                   <LayoutDashboard className="size-4" />
                   {t('sidebar.dashboard')}
                 </Link>
@@ -281,7 +287,7 @@ export function AppSidebar({
             <SidebarMenu className="gap-2">
               <SidebarMenuItem>
                 <SidebarMenuButton asChild isActive={pathname.startsWith('/settings')}>
-                  <Link href="/settings" className="font-medium">
+                  <Link href="/settings" className="font-medium" onClick={closeMobileSidebar}>
                     <Settings className="size-4" />
                     {t('sidebar.settings')}
                   </Link>
@@ -297,7 +303,7 @@ export function AppSidebar({
             <SidebarMenu className="gap-2">
               <SidebarMenuItem>
                 <SidebarMenuButton asChild isActive={pathname.startsWith('/admin')}>
-                  <Link href="/admin" className="font-medium">
+                  <Link href="/admin" className="font-medium" onClick={closeMobileSidebar}>
                     <ShieldCheck className="size-4" />
                     {t('sidebar.adminPanel')}
                   </Link>