From dc5bbaa0aefb17eb4c38bc658038eff75d6d9a7b Mon Sep 17 00:00:00 2001 From: Marco Sadjadi Date: Sat, 23 May 2026 00:19:31 +0200 Subject: [PATCH] feat(web): mobile bottom action bar for + New server On phones the dashboard top bar is tight with the nav icons + the primary action crammed alongside. Move the action into a sticky bottom bar in the thumb zone, leave the top bar to navigation. Hidden on the create-wizard route since that page owns its own action. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/app/(dashboard)/layout.tsx | 6 ++-- apps/web/components/mobile-action-bar.tsx | 35 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 apps/web/components/mobile-action-bar.tsx diff --git a/apps/web/app/(dashboard)/layout.tsx b/apps/web/app/(dashboard)/layout.tsx index 0072ec3..2140380 100644 --- a/apps/web/app/(dashboard)/layout.tsx +++ b/apps/web/app/(dashboard)/layout.tsx @@ -1,4 +1,5 @@ import { Logo } from '@/components/logo'; +import { MobileActionBar } from '@/components/mobile-action-bar'; import { FileClock, LayoutGrid, Package, Server, Settings } from 'lucide-react'; import Link from 'next/link'; @@ -29,13 +30,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod + New server -
{children}
+
{children}
+ ); } diff --git a/apps/web/components/mobile-action-bar.tsx b/apps/web/components/mobile-action-bar.tsx new file mode 100644 index 0000000..ccce73c --- /dev/null +++ b/apps/web/components/mobile-action-bar.tsx @@ -0,0 +1,35 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; + +// Routes where the global "+ New server" CTA makes no sense — the wizard is +// itself the create flow and owns its own primary action. +const HIDDEN_PATHS: readonly string[] = ['/servers/new']; + +/** + * Mobile-only sticky bottom action bar. On phones the dashboard top header is + * already tight with the nav icons; the primary action belongs in the thumb + * zone, not crammed top-right. Hidden from `sm:` upward where the header has + * room for the same button inline. + */ +export function MobileActionBar() { + const pathname = usePathname(); + if (HIDDEN_PATHS.includes(pathname)) return null; + + return ( +
+
+ + + New server + +
+
+ ); +}