From 4aacffdf4ee53b5eff32c307df62bae1a1a99e6c Mon Sep 17 00:00:00 2001 From: tonym Date: Tue, 16 Jun 2026 02:16:57 -0500 Subject: [PATCH] =?UTF-8?q?v0.17.0=20=E2=80=94=20Mobile=20menu=20(#3)=20+?= =?UTF-8?q?=20Suggestions=20readability=20&=20header=20lightbulb=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Gitea suggestions #3 and #2. #3 Mobile: the sidebar was desktop-only (hidden md:flex), leaving phones with no nav at all. Added a slide-out drawer (hamburger in the top bar, md:hidden) that reuses the nav. Lifted navItems/settingsItems into src/components/layout/nav-items.ts so the sidebar and mobile drawer share one source + active-state logic. No HTTPS needed (pure responsive UI). #2 Suggestions: the @otm/account-panel SuggestionsPanel hardcodes light neutral-* text that assumes a white backdrop, so it washed out on the app theme — now rendered on a white card for contrast in light/dark. Added a lightbulb button in the header that jumps to Suggestions (like FMB). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/(dashboard)/suggestions/page.tsx | 8 ++- src/components/layout/mobile-nav.tsx | 76 ++++++++++++++++++++++++ src/components/layout/nav-items.ts | 34 +++++++++++ src/components/layout/sidebar.tsx | 32 +--------- src/components/layout/topnav.tsx | 58 ++++++++++-------- src/lib/changelog.ts | 8 +++ src/lib/version.ts | 2 +- 7 files changed, 163 insertions(+), 55 deletions(-) create mode 100644 src/components/layout/mobile-nav.tsx create mode 100644 src/components/layout/nav-items.ts diff --git a/src/app/(dashboard)/suggestions/page.tsx b/src/app/(dashboard)/suggestions/page.tsx index a8522fa..81f5f66 100644 --- a/src/app/(dashboard)/suggestions/page.tsx +++ b/src/app/(dashboard)/suggestions/page.tsx @@ -9,5 +9,11 @@ export const dynamic = "force-dynamic"; export default async function SuggestionsPage() { const session = await getSession(); if (!session) redirect("/login"); - return ; + // The panel uses light-gray (neutral-*) text that assumes a white backdrop, so + // render it on a white card for readable contrast in light or dark theme. + return ( +
+ +
+ ); } diff --git a/src/components/layout/mobile-nav.tsx b/src/components/layout/mobile-nav.tsx new file mode 100644 index 0000000..9b5618f --- /dev/null +++ b/src/components/layout/mobile-nav.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { Menu, X, Leaf } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { navItems, settingsItems, isNavActive } from "./nav-items"; + +export function MobileNav() { + const [open, setOpen] = useState(false); + const path = usePathname(); + + useEffect(() => { setOpen(false); }, [path]); // close on navigation + useEffect(() => { + document.body.style.overflow = open ? "hidden" : ""; + return () => { document.body.style.overflow = ""; }; + }, [open]); + + const linkClass = (active: boolean, inactive: string) => + cn( + "flex items-center gap-2.5 px-3 py-2.5 rounded-md text-sm transition-colors", + active + ? "bg-[hsl(var(--canopy-accent))] text-[hsl(var(--canopy-accent-foreground))]" + : inactive, + ); + const MAIN_INACTIVE = "hover:bg-[hsl(var(--canopy-accent))] text-[hsl(var(--canopy-foreground))]/80"; + const SETTINGS_INACTIVE = "hover:bg-[hsl(var(--canopy-accent))] text-[hsl(var(--canopy-foreground))]/60"; + + return ( + <> + + + {open && ( +
+
setOpen(false)} /> + +
+ )} + + ); +} diff --git a/src/components/layout/nav-items.ts b/src/components/layout/nav-items.ts new file mode 100644 index 0000000..685bb46 --- /dev/null +++ b/src/components/layout/nav-items.ts @@ -0,0 +1,34 @@ +import { + LayoutDashboard, Leaf, BookOpen, Package, PawPrint, MapPin, Bell, + Lightbulb, KeyRound, HardDrive, RefreshCw, type LucideIcon, +} from "lucide-react"; + +export type NavItem = { label: string; href: string; icon: LucideIcon }; + +export const navItems: NavItem[] = [ + { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard }, + { label: "Garden", href: "/garden", icon: Leaf }, + { label: "Plant types", href: "/garden/types", icon: BookOpen }, + { label: "Inventory", href: "/inventory", icon: Package }, + { label: "Animals", href: "/animals", icon: PawPrint }, + { label: "Locations", href: "/locations", icon: MapPin }, + { label: "Reminders", href: "/tasks", icon: Bell }, +]; + +export const settingsItems: NavItem[] = [ + { label: "Suggestions", href: "/suggestions", icon: Lightbulb }, + { label: "API tokens", href: "/settings/tokens", icon: KeyRound }, + { label: "Backup & Restore", href: "/settings/backup", icon: HardDrive }, + { label: "Updates", href: "/settings/updates", icon: RefreshCw }, +]; + +// Active on exact match or a sub-path, unless a more specific item (e.g. +// /garden/types) already claims the current path. +export function isNavActive(path: string, href: string, all: NavItem[]): boolean { + if (path === href) return true; + if (href === "/dashboard") return false; + if (!path.startsWith(href + "/")) return false; + return !all.some( + (o) => o.href.length > href.length && (path === o.href || path.startsWith(o.href + "/")), + ); +} diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index 737ce4e..0ae4704 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -2,28 +2,9 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Leaf, Home, Wrench, LayoutDashboard, Bell, HardDrive, Lightbulb, RefreshCw, BookOpen, KeyRound, Package, PawPrint, MapPin } from "lucide-react"; +import { Leaf } from "lucide-react"; import { cn } from "@/lib/utils"; - -const navItems = [ - { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard }, - { label: "Garden", href: "/garden", icon: Leaf }, - { label: "Plant types", href: "/garden/types", icon: BookOpen }, - { label: "Inventory", href: "/inventory", icon: Package }, - { label: "Animals", href: "/animals", icon: PawPrint }, - { label: "Locations", href: "/locations", icon: MapPin }, - { label: "Reminders", href: "/tasks", icon: Bell }, - // Home maintenance and Equipment come in future releases - // { label: "Home", href: "/home", icon: Home }, - // { label: "Equipment", href: "/equipment", icon: Wrench }, -]; - -const settingsItems = [ - { label: "Suggestions", href: "/suggestions", icon: Lightbulb }, - { label: "API tokens", href: "/settings/tokens", icon: KeyRound }, - { label: "Backup & Restore", href: "/settings/backup", icon: HardDrive }, - { label: "Updates", href: "/settings/updates", icon: RefreshCw }, -]; +import { navItems, settingsItems, isNavActive } from "./nav-items"; export function Sidebar() { const path = usePathname(); @@ -39,14 +20,7 @@ export function Sidebar() {