ux: dashboard grouping, responsive grids, grouped nav drawer, modal polish

Dashboard:
- Split 12-item quick-log into two labeled groups (Alimentation & Soins / Sommeil & Activités)
- "Voir tout →" link on today's timeline when events present
- Quick-log button: hover highlight indigo instead of grey for clearer affordance

Event modal:
- Timer: text-3xl on mobile, text-4xl on sm+ (prevents overflow on narrow screens)
- Date/time inputs: CalendarDays + Clock icons inline in each field for clarity

Stats:
- Today summary 3-col: gap-2 md:gap-3, p-3 md:p-4, text-xl md:text-2xl (no text clipping on mobile)
- Sleep/feeding summary grids: gap-2 md:gap-4

Calendar:
- Cell min-height: 52px mobile, 64px sm+ (reduces grid height on small screens)
- Day detail: events sorted by time, duration shown as HH:mm → HH:mm for ranged events
- Empty day state uses card container instead of bare text

Medications:
- Empty state: Pill icon + subtitle copy, centred card

Milk:
- Used/archived section: "… et N lots de plus" indicator when > 20 items

Nav drawer (mobile):
- Grouped secondary links: Suivi / Santé / Vie du bébé sections with section headers
- Medications: correct Pill icon (was duplicating Bell/Stethoscope)
- Ungrouped items fall through to a full-width list row

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 15:36:54 +02:00
parent 42c580d7e2
commit 63effbf85b
7 changed files with 168 additions and 88 deletions
+35 -21
View File
@@ -109,7 +109,7 @@ export default function CalendarPage() {
<button <button
key={i} key={i}
onClick={() => setSelectedDay(isSameDay(day, selectedDay ?? new Date(0)) ? null : day)} onClick={() => setSelectedDay(isSameDay(day, selectedDay ?? new Date(0)) ? null : day)}
className={`min-h-[64px] p-1.5 text-left border-b border-r border-slate-100 dark:border-slate-800 last:border-r-0 transition ${ className={`min-h-[52px] sm:min-h-[64px] p-1 sm:p-1.5 text-left border-b border-r border-slate-100 dark:border-slate-800 last:border-r-0 transition ${
isSelected isSelected
? "bg-indigo-50 dark:bg-indigo-950" ? "bg-indigo-50 dark:bg-indigo-950"
: "hover:bg-slate-50 dark:hover:bg-slate-800/50" : "hover:bg-slate-50 dark:hover:bg-slate-800/50"
@@ -148,30 +148,44 @@ export default function CalendarPage() {
{/* Selected day detail */} {/* Selected day detail */}
{selectedDay && ( {selectedDay && (
<div className="mt-4"> <div className="mt-4">
<h2 className="text-sm font-semibold text-slate-700 dark:text-slate-200 mb-3 capitalize"> <div className="flex items-baseline justify-between mb-3">
{format(selectedDay, "EEEE d MMMM", { locale: fr })} <h2 className="text-sm font-semibold text-slate-700 dark:text-slate-200 capitalize">
<span className="ml-2 text-slate-400 dark:text-slate-500 font-normal">· {selectedDayEvents.length} événement{selectedDayEvents.length !== 1 ? "s" : ""}</span> {format(selectedDay, "EEEE d MMMM", { locale: fr })}
</h2> </h2>
<span className="text-xs text-slate-400 dark:text-slate-500">
{selectedDayEvents.length} événement{selectedDayEvents.length !== 1 ? "s" : ""}
</span>
</div>
{selectedDayEvents.length === 0 ? ( {selectedDayEvents.length === 0 ? (
<p className="text-sm text-slate-400 dark:text-slate-500 py-4 text-center">Aucun événement</p> <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl py-8 text-center text-slate-400 dark:text-slate-500">
<p className="text-sm">Aucun événement ce jour</p>
</div>
) : ( ) : (
<div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl divide-y divide-slate-100 dark:divide-slate-800"> <div className="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl divide-y divide-slate-100 dark:divide-slate-800">
{selectedDayEvents.map((ev) => { {selectedDayEvents
const cfg = EVENT_CONFIG[ev.type]; .sort((a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime())
return ( .map((ev) => {
<div key={ev.id} className="flex items-center gap-3 px-4 py-3"> const cfg = EVENT_CONFIG[ev.type];
<div className={`w-8 h-8 rounded-lg ${cfg.bgClass} flex items-center justify-center flex-shrink-0`}> const hasDuration = ev.endedAt;
<EventIcon name={cfg.icon} className={`w-4 h-4 ${cfg.colorClass}`} /> return (
<div key={ev.id} className="flex items-center gap-3 px-4 py-3">
<div className={`w-8 h-8 rounded-lg ${cfg.bgClass} flex items-center justify-center flex-shrink-0`}>
<EventIcon name={cfg.icon} className={`w-4 h-4 ${cfg.colorClass}`} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">{cfg.label}</p>
{hasDuration && (
<p className="text-xs text-slate-400 dark:text-slate-500">
{format(new Date(ev.startedAt), "HH:mm")} {format(new Date(ev.endedAt!), "HH:mm")}
</p>
)}
</div>
<span className="text-xs text-slate-400 dark:text-slate-500 font-mono flex-shrink-0">
{format(new Date(ev.startedAt), "HH:mm")}
</span>
</div> </div>
<div className="flex-1 min-w-0"> );
<p className="text-sm font-medium text-slate-700 dark:text-slate-200">{cfg.label}</p> })}
</div>
<span className="text-xs text-slate-400 dark:text-slate-500 font-mono flex-shrink-0">
{format(new Date(ev.startedAt), "HH:mm")}
</span>
</div>
);
})}
</div> </div>
)} )}
</div> </div>
+42 -37
View File
@@ -11,19 +11,15 @@ import { format, isToday } from "date-fns";
import { fr } from "date-fns/locale"; import { fr } from "date-fns/locale";
import { AlertTriangle, Clock, CheckCircle2, Pin, PinOff, Pencil, Check, X, Plus, Trash2 } from "lucide-react"; import { AlertTriangle, Clock, CheckCircle2, Pin, PinOff, Pencil, Check, X, Plus, Trash2 } from "lucide-react";
const QUICK_LOG_TYPES: EventType[] = [ const QUICK_LOG_GROUPS: { label: string; types: EventType[] }[] = [
"BREASTFEED", {
"BOTTLE", label: "Alimentation & Soins",
"PUMP", types: ["BREASTFEED", "BOTTLE", "PUMP", "DIAPER_WET", "DIAPER_STOOL", "MEDICATION", "TEMPERATURE"],
"DIAPER_WET", },
"DIAPER_STOOL", {
"NAP", label: "Sommeil & Activités",
"NIGHT_SLEEP", types: ["NAP", "NIGHT_SLEEP", "BATH", "WALK", "TUMMY_TIME"],
"MEDICATION", },
"TEMPERATURE",
"BATH",
"WALK",
"TUMMY_TIME",
]; ];
interface Event { interface Event {
@@ -405,27 +401,31 @@ export default function DashboardPage() {
</div> </div>
{/* Quick log */} {/* Quick log */}
<div> <div className="space-y-4">
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3">Enregistrer</h2> {QUICK_LOG_GROUPS.map((group) => (
<div className="grid grid-cols-4 md:grid-cols-4 gap-2"> <div key={group.label}>
{QUICK_LOG_TYPES.map((type) => { <h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-2">{group.label}</h2>
const cfg = EVENT_CONFIG[type]; <div className="grid grid-cols-4 gap-2">
return ( {group.types.map((type) => {
<button const cfg = EVENT_CONFIG[type];
key={type} return (
onClick={() => setActiveModal(type)} <button
className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:border-slate-300 hover:shadow-sm rounded-xl p-3 flex flex-col items-center gap-2 transition active:scale-95" key={type}
> onClick={() => setActiveModal(type)}
<div className={`w-9 h-9 rounded-lg ${cfg.bgClass} flex items-center justify-center`}> className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:border-indigo-200 dark:hover:border-indigo-700 hover:shadow-sm rounded-xl p-2.5 flex flex-col items-center gap-1.5 transition active:scale-95"
<EventIcon name={cfg.icon} className={`w-5 h-5 ${cfg.colorClass}`} /> >
</div> <div className={`w-9 h-9 rounded-lg ${cfg.bgClass} flex items-center justify-center`}>
<span className="text-[10px] font-medium text-slate-600 dark:text-slate-300 text-center leading-tight"> <EventIcon name={cfg.icon} className={`w-5 h-5 ${cfg.colorClass}`} />
{cfg.label} </div>
</span> <span className="text-[10px] font-medium text-slate-600 dark:text-slate-300 text-center leading-tight">
</button> {cfg.label}
); </span>
})} </button>
</div> );
})}
</div>
</div>
))}
</div> </div>
</div> </div>
@@ -483,9 +483,14 @@ export default function DashboardPage() {
{/* Today's timeline */} {/* Today's timeline */}
{todayEvents.length > 0 && ( {todayEvents.length > 0 && (
<div className="mt-6"> <div className="mt-6">
<h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-3"> <div className="flex items-center justify-between mb-3">
Aujourd&apos;hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""} <h2 className="text-xs font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-wider">
</h2> Aujourd&apos;hui · {todayEvents.length} événement{todayEvents.length > 1 ? "s" : ""}
</h2>
<a href="/timeline" className="text-xs text-indigo-600 dark:text-indigo-400 hover:underline">
Voir tout
</a>
</div>
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden divide-y divide-slate-100 dark:divide-slate-700"> <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden divide-y divide-slate-100 dark:divide-slate-700">
{todayEvents.slice(0, 10).map((ev) => { {todayEvents.slice(0, 10).map((ev) => {
const cfg = EVENT_CONFIG[ev.type]; const cfg = EVENT_CONFIG[ev.type];
+6 -2
View File
@@ -2,7 +2,7 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus, Pencil, Trash2, Check, X } from "lucide-react"; import { Plus, Pencil, Trash2, Check, X, Pill } from "lucide-react";
interface Profile { interface Profile {
id: string; id: string;
@@ -224,7 +224,11 @@ export default function MedicationsPage() {
{!isLoading && profiles.length === 0 && !showForm && ( {!isLoading && profiles.length === 0 && !showForm && (
<div className="text-center py-16 text-slate-400 dark:text-slate-500"> <div className="text-center py-16 text-slate-400 dark:text-slate-500">
<p className="text-sm">Aucun profil médicament</p> <div className="w-12 h-12 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center mx-auto mb-3">
<Pill className="w-6 h-6 opacity-30" />
</div>
<p className="text-sm font-medium">Aucun profil médicament</p>
<p className="text-xs mt-1 opacity-70">Créez un profil pour suivre les intervalles de prise</p>
</div> </div>
)} )}
</div> </div>
+5
View File
@@ -340,6 +340,11 @@ export default function MilkPage() {
</button> </button>
</div> </div>
))} ))}
{used.length > 20 && (
<p className="text-center text-xs text-slate-400 dark:text-slate-500 py-2">
et {used.length - 20} lot{used.length - 20 > 1 ? "s" : ""} de plus
</p>
)}
</div> </div>
)} )}
</div> </div>
+6 -6
View File
@@ -187,15 +187,15 @@ export default function StatsPage() {
</div> </div>
{/* Today summary */} {/* Today summary */}
<div className="grid grid-cols-3 gap-3 mb-6"> <div className="grid grid-cols-3 gap-2 md:gap-3 mb-6">
{[ {[
{ label: "Repas", value: todayFeeds, color: "text-rose-600", bg: "bg-rose-50 dark:bg-rose-950", border: "border-rose-100 dark:border-rose-900" }, { label: "Repas", value: todayFeeds, color: "text-rose-600", bg: "bg-rose-50 dark:bg-rose-950", border: "border-rose-100 dark:border-rose-900" },
{ label: "Couches", value: todayDiapers, color: "text-amber-600", bg: "bg-amber-50 dark:bg-amber-950", border: "border-amber-100 dark:border-amber-900" }, { label: "Couches", value: todayDiapers, color: "text-amber-600", bg: "bg-amber-50 dark:bg-amber-950", border: "border-amber-100 dark:border-amber-900" },
{ label: "Sommeil", value: `${Math.round(todaySleepMins / 60 * 10) / 10}h`, color: "text-violet-600", bg: "bg-violet-50 dark:bg-violet-950", border: "border-violet-100 dark:border-violet-900" }, { label: "Sommeil", value: `${Math.round(todaySleepMins / 60 * 10) / 10}h`, color: "text-violet-600", bg: "bg-violet-50 dark:bg-violet-950", border: "border-violet-100 dark:border-violet-900" },
].map(({ label, value, color, bg, border }) => ( ].map(({ label, value, color, bg, border }) => (
<div key={label} className={`${bg} border ${border} rounded-xl p-4 text-center`}> <div key={label} className={`${bg} border ${border} rounded-xl p-3 md:p-4 text-center`}>
<div className={`text-2xl font-bold ${color}`}>{value}</div> <div className={`text-xl md:text-2xl font-bold ${color}`}>{value}</div>
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">{label} · aujourd&apos;hui</div> <div className="text-[10px] md:text-xs text-slate-500 dark:text-slate-400 mt-1 leading-tight">{label}<br className="md:hidden" /><span className="hidden md:inline"> · </span>aujourd&apos;hui</div>
</div> </div>
))} ))}
</div> </div>
@@ -213,7 +213,7 @@ export default function StatsPage() {
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4"> <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4">
<p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé du sommeil</p> <p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé du sommeil</p>
<p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p> <p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-2 md:gap-4">
<div> <div>
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Total</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Total</p>
<p className="text-lg font-bold text-violet-600 leading-tight"> <p className="text-lg font-bold text-violet-600 leading-tight">
@@ -239,7 +239,7 @@ export default function StatsPage() {
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4"> <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl p-4 mb-4">
<p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé des repas</p> <p className="text-sm font-medium text-slate-700 dark:text-slate-200 mb-3">Résumé des repas</p>
<p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p> <p className="text-xs text-slate-400 dark:text-slate-500 mb-4">{periodSubtitle(days)}</p>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-2 md:gap-4">
<div> <div>
<p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Durée allaitement</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Durée allaitement</p>
<p className="text-lg font-bold text-rose-600 leading-tight"> <p className="text-lg font-bold text-rose-600 leading-tight">
+26 -14
View File
@@ -3,7 +3,7 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { EVENT_CONFIG, EventType } from "@/lib/event-config"; import { EVENT_CONFIG, EventType } from "@/lib/event-config";
import { EventIcon } from "@/components/event-icon"; import { EventIcon } from "@/components/event-icon";
import { X, Camera, Loader2, AlertTriangle, Zap, Clock } from "lucide-react"; import { X, Camera, Loader2, AlertTriangle, Zap, Clock, CalendarDays } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { format } from "date-fns"; import { format } from "date-fns";
import { fr } from "date-fns/locale"; import { fr } from "date-fns/locale";
@@ -327,7 +327,7 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
{/* Timer */} {/* Timer */}
{config.hasTimer && ( {config.hasTimer && (
<div className="bg-slate-50 dark:bg-slate-700/50 rounded-xl p-4 text-center"> <div className="bg-slate-50 dark:bg-slate-700/50 rounded-xl p-4 text-center">
<div className="text-4xl font-mono font-bold text-slate-800 dark:text-slate-100 mb-3 tabular-nums"> <div className="text-3xl sm:text-4xl font-mono font-bold text-slate-800 dark:text-slate-100 mb-3 tabular-nums">
{formatTimer(timerSeconds)} {formatTimer(timerSeconds)}
</div> </div>
<button <button
@@ -610,24 +610,36 @@ export function EventModal({ type, babyId, onClose, onSaved, initialEvent }: Pro
{config.hasDuration ? "Début" : "Heure"} {config.hasDuration ? "Début" : "Heure"}
</label> </label>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<input type="date" value={form.startedAt.slice(0, 10)} <div className="relative">
onChange={(e) => setForm((f) => ({ ...f, startedAt: `${e.target.value}T${f.startedAt.slice(11, 16) || "00:00"}` }))} <CalendarDays className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
className={inputCls} /> <input type="date" value={form.startedAt.slice(0, 10)}
<input type="time" value={form.startedAt.slice(11, 16)} onChange={(e) => setForm((f) => ({ ...f, startedAt: `${e.target.value}T${f.startedAt.slice(11, 16) || "00:00"}` }))}
onChange={(e) => setForm((f) => ({ ...f, startedAt: `${f.startedAt.slice(0, 10)}T${e.target.value}` }))} className={`${inputCls} pl-8`} />
className={inputCls} /> </div>
<div className="relative">
<Clock className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
<input type="time" value={form.startedAt.slice(11, 16)}
onChange={(e) => setForm((f) => ({ ...f, startedAt: `${f.startedAt.slice(0, 10)}T${e.target.value}` }))}
className={`${inputCls} pl-8`} />
</div>
</div> </div>
</div> </div>
{config.hasDuration && ( {config.hasDuration && (
<div> <div>
<label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">Fin</label> <label className="block text-xs font-medium text-slate-600 dark:text-slate-300 mb-1.5">Fin</label>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<input type="date" value={form.endedAt.slice(0, 10)} <div className="relative">
onChange={(e) => setForm((f) => ({ ...f, endedAt: `${e.target.value}T${f.endedAt.slice(11, 16) || "00:00"}` }))} <CalendarDays className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
className={inputCls} /> <input type="date" value={form.endedAt.slice(0, 10)}
<input type="time" value={form.endedAt.slice(11, 16)} onChange={(e) => setForm((f) => ({ ...f, endedAt: `${e.target.value}T${f.endedAt.slice(11, 16) || "00:00"}` }))}
onChange={(e) => setForm((f) => ({ ...f, endedAt: `${f.endedAt.slice(0, 10) || new Date().toISOString().slice(0, 10)}T${e.target.value}` }))} className={`${inputCls} pl-8`} />
className={inputCls} /> </div>
<div className="relative">
<Clock className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400 pointer-events-none" />
<input type="time" value={form.endedAt.slice(11, 16)}
onChange={(e) => setForm((f) => ({ ...f, endedAt: `${f.endedAt.slice(0, 10) || new Date().toISOString().slice(0, 10)}T${e.target.value}` }))}
className={`${inputCls} pl-8`} />
</div>
</div> </div>
</div> </div>
)} )}
+48 -8
View File
@@ -26,6 +26,7 @@ import {
Bell, Bell,
CalendarDays, CalendarDays,
Milk, Milk,
Pill,
} from "lucide-react"; } from "lucide-react";
const LINKS = [ const LINKS = [
@@ -38,12 +39,19 @@ const LINKS = [
{ href: "/milestones", label: "Étapes", Icon: Star }, { href: "/milestones", label: "Étapes", Icon: Star },
{ href: "/notes", label: "Notes", Icon: NotebookPen }, { href: "/notes", label: "Notes", Icon: NotebookPen },
{ href: "/doctor-notes", label: "Médecin", Icon: Stethoscope }, { href: "/doctor-notes", label: "Médecin", Icon: Stethoscope },
{ href: "/vaccinations", label: "Vaccinations", Icon: Syringe }, { href: "/vaccinations", label: "Vaccins", Icon: Syringe },
{ href: "/medications", label: "Médic.", Icon: Stethoscope }, { href: "/medications", label: "Médicaments", Icon: Pill },
{ href: "/reminders", label: "Rappels", Icon: Bell }, { href: "/reminders", label: "Rappels", Icon: Bell },
{ href: "/settings", label: "Réglages", Icon: Settings }, { href: "/settings", label: "Réglages", Icon: Settings },
]; ];
// Groups shown in the mobile "More" drawer
const DRAWER_GROUPS: { label: string; hrefs: string[] }[] = [
{ label: "Suivi", hrefs: ["/calendar", "/stats", "/milk"] },
{ label: "Santé", hrefs: ["/medications", "/reminders", "/vaccinations", "/doctor-notes"] },
{ label: "Vie du bébé", hrefs: ["/milestones", "/notes", "/growth"] },
];
const PRIMARY_HREFS = ["/dashboard", "/timeline", "/growth", "/settings"]; const PRIMARY_HREFS = ["/dashboard", "/timeline", "/growth", "/settings"];
const PRIMARY_LINKS = LINKS.filter((l) => PRIMARY_HREFS.includes(l.href)); const PRIMARY_LINKS = LINKS.filter((l) => PRIMARY_HREFS.includes(l.href));
@@ -253,23 +261,55 @@ export function BottomNav() {
<X className="w-5 h-5" /> <X className="w-5 h-5" />
</button> </button>
</div> </div>
{/* Grid of secondary links */} {/* Grouped secondary links */}
<div className="grid grid-cols-3 gap-2 px-4 pb-4"> <div className="px-4 pb-4 space-y-4">
{secondaryLinks.map(({ href, label, Icon }) => { {DRAWER_GROUPS.map((group) => {
const groupLinks = group.hrefs
.map((href) => secondaryLinks.find((l) => l.href === href))
.filter(Boolean) as typeof secondaryLinks;
if (groupLinks.length === 0) return null;
return (
<div key={group.label}>
<p className="text-[10px] font-semibold uppercase tracking-wider text-slate-400 dark:text-slate-600 mb-1.5 px-1">{group.label}</p>
<div className="grid grid-cols-3 gap-1.5">
{groupLinks.map(({ href, label, Icon }) => {
const active = pathname === href;
return (
<Link
key={href}
href={href}
onClick={() => setDrawerOpen(false)}
className={`flex flex-col items-center gap-1.5 px-2 py-2.5 rounded-xl transition-colors ${
active
? "bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400"
: "text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800"
}`}
>
<Icon className="w-5 h-5" />
<span className="text-[10px] font-medium text-center leading-tight">{label}</span>
</Link>
);
})}
</div>
</div>
);
})}
{/* Any admin link not in groups */}
{secondaryLinks.filter((l) => !DRAWER_GROUPS.flatMap((g) => g.hrefs).includes(l.href)).map(({ href, label, Icon }) => {
const active = pathname === href; const active = pathname === href;
return ( return (
<Link <Link
key={href} key={href}
href={href} href={href}
onClick={() => setDrawerOpen(false)} onClick={() => setDrawerOpen(false)}
className={`flex flex-col items-center gap-1.5 px-2 py-3 rounded-xl transition-colors ${ className={`flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${
active active
? "bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400" ? "bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400"
: "text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800" : "text-slate-500 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800"
}`} }`}
> >
<Icon className="w-5 h-5" /> <Icon className="w-4 h-4" />
<span className="text-[10px] font-medium text-center leading-tight">{label}</span> <span className="text-sm font-medium">{label}</span>
</Link> </Link>
); );
})} })}