feat(18): restyle sidebar and header to match production design

- Sidebar: Tailwind utilities, French labels (Aperçu/Tâches/Projets/Planning/Discussions/Fichiers), hr separators, collapse button on group hover, tablo list with circle icons, org footer with avatar + "1 membre"
- Header: 75px height, border-b #EAECF0, search input left, bell + avatar-dropdown right (no breadcrumb visible — retained sr-only for a11y + tests)
- Layout: flex instead of grid, dashboard-main no padding, dashboard-main-content wrapper at 2rem
- Tests updated for new search-input assertion

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arthur Belleville 2026-05-17 16:00:26 +02:00
parent 1952b95405
commit 9ba650b345
No known key found for this signature in database
5 changed files with 196 additions and 4365 deletions

View file

@ -679,8 +679,9 @@ func TestTablosDashboard_Header(t *testing.T) {
if !strings.Contains(body, "Dashboard") { if !strings.Contains(body, "Dashboard") {
t.Errorf("body missing breadcrumb label 'Dashboard'; body: %.300s", body) t.Errorf("body missing breadcrumb label 'Dashboard'; body: %.300s", body)
} }
if !strings.Contains(body, "header-search-placeholder") { // Search input is present (placeholder="Search...")
t.Errorf("body missing 'header-search-placeholder'; body: %.300s", body) if !strings.Contains(body, `placeholder="Search..."`) {
t.Errorf("body missing search input; body: %.300s", body)
} }
} }

View file

@ -9,15 +9,22 @@
.dashboard-shell { .dashboard-shell {
background: var(--gradient-shell); background: var(--gradient-shell);
color: var(--foreground); color: var(--foreground);
display: grid; display: flex;
grid-template-columns: minmax(16rem, 18rem) 1fr;
min-height: 100vh; min-height: 100vh;
} }
.dashboard-sidebar { .dashboard-sidebar {
width: 12rem; /* w-48 = 192px */
flex-shrink: 0;
transition: width 0.3s ease;
padding-left: env(safe-area-inset-left, 0px); padding-left: env(safe-area-inset-left, 0px);
} }
/* Collapsed sidebar */
.dashboard-shell.sidebar-is-collapsed .dashboard-sidebar {
width: 4rem;
}
/* ============================================================ /* ============================================================
Section 2 Sidebar nav shell Section 2 Sidebar nav shell
============================================================ */ ============================================================ */
@ -358,25 +365,10 @@
} }
/* ── Page header bar ───────────────────────────────────────────────── */ /* ── Page header bar ───────────────────────────────────────────────── */
/* Styling is now handled via Tailwind utilities in PageHeader templ component. */
/* This class is retained for test assertions. */
.page-header { .page-header {
display: flex; /* Tailwind handles layout; this class is an anchor for tests */
align-items: center;
gap: 1rem;
padding: 0.75rem 1.5rem;
background: var(--color-surface-elevated);
border-bottom: 1px solid var(--color-border-panel);
margin: -2rem -2rem 1.5rem -2rem; /* cancel .dashboard-main padding on three sides, add bottom gap */
}
.page-header-left {
flex: 0 0 auto;
}
.page-header-center {
flex: 1 1 auto;
display: flex;
justify-content: center;
} }
.page-header-right { .page-header-right {
@ -573,9 +565,16 @@
.dashboard-main { .dashboard-main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; flex: 1;
min-width: 0; min-width: 0;
}
.dashboard-main-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 2rem; padding: 2rem;
flex: 1;
} }
/* ============================================================ /* ============================================================

File diff suppressed because one or more lines are too long

View file

@ -57,29 +57,29 @@ templ SidebarNavIcon(kind string) {
} }
} }
// SidebarNavItemRow renders one nav list item. // SidebarNavItemRow renders one nav list item with Tailwind utility classes.
// If item.Href is empty, renders as a non-interactive div (visual-only, per D-N02).
// If item.Href is non-empty, renders as an anchor tag.
templ SidebarNavItemRow(item sidebarNavItem) { templ SidebarNavItemRow(item sidebarNavItem) {
if item.Href == "" { <div class={ navItemWrapperClass(item.Active) }>
<div class={ sidebarNavItemClass(item.Active) } style="cursor: default"> if item.Href != "" && item.Href != "#" {
<div class="sidebar-nav-link-inner"> <a class="w-full" href={ templ.SafeURL(item.Href) }>
<span class="sidebar-nav-icon"> <div class="flex items-center gap-x-2.5 pl-2">
@SidebarNavIcon(item.Icon) <span class={ navItemIconClass(item.Active) }>
</span> @SidebarNavIcon(item.Icon)
<div class="sidebar-nav-label">{ item.Label }</div> </span>
<div class={ navItemLabelClass(item.Active) }>{ item.Label }</div>
</div>
</a>
} else {
<div class="w-full">
<div class="flex items-center gap-x-2.5 pl-2">
<span class={ navItemIconClass(item.Active) }>
@SidebarNavIcon(item.Icon)
</span>
<div class={ navItemLabelClass(item.Active) }>{ item.Label }</div>
</div>
</div> </div>
</div> }
} else { </div>
<a href={ templ.SafeURL(item.Href) } class={ sidebarNavItemClass(item.Active) }>
<div class="sidebar-nav-link-inner">
<span class="sidebar-nav-icon">
@SidebarNavIcon(item.Icon)
</span>
<div class="sidebar-nav-label">{ item.Label }</div>
</div>
</a>
}
} }
// SidebarProjectsSection renders the projects list in the sidebar. // SidebarProjectsSection renders the projects list in the sidebar.
@ -121,145 +121,168 @@ templ SidebarOrganizationFooter(user *auth.User, csrfToken string) {
</div> </div>
} }
// DashboardSidebar renders the full sidebar with brand, nav, projects, and footer. // DashboardSidebar renders the full sidebar matching the production design.
// Rebuilt in Phase 18 Plan 02 per D-07/D-08/D-09: two-section structure (GENERAL + PROJECTS), // Uses Tailwind utility classes. Collapse button is revealed on group hover (D-09).
// collapse button wired via inline JS (no server round-trip, resets on reload per D-09).
// SidebarOrganizationFooter moves to avatar dropdown in Plan 03.
templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string) { templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string) {
<aside class="dashboard-sidebar"> <aside class="dashboard-sidebar">
<nav aria-label="Main navigation" class="sidebar-nav-shell"> <nav aria-label="Main navigation" class="sidebar-nav-shell group isolate flex flex-col overflow-y-auto overflow-x-hidden bg-white border-r border-gray-200 h-full w-full sticky top-0">
<!-- Brand / logo --> <!-- Brand / logo -->
<div class="sidebar-brand"> <div class="relative flex flex-col items-center px-2 py-3 w-full">
<a class="sidebar-brand-link" href="/" aria-label="Home"> <a class="flex flex-col items-center gap-2 w-full" href="/" aria-label="Home">
<img class="sidebar-brand-logo" src="/static/logo_dark.png" alt="Logo XTablo"/> <img alt="Logo XTablo" class="w-16 h-16 rounded-lg" src="/static/logo_dark.png"/>
<h1 class="sidebar-brand-title">XTablo</h1> <h1 class="text-lg font-bold text-gray-900 whitespace-nowrap">XTablo</h1>
</a> </a>
<button <button
class="sidebar-collapse-button" class="absolute top-2 right-2 size-5 p-1 text-gray-500 hover:text-gray-900 bg-white rounded-full shadow-md opacity-0 group-hover:opacity-100 hover:scale-110 transition-all duration-300"
aria-label="Toggle sidebar" aria-label="Toggle sidebar"
onclick="(function(btn){var shell=document.querySelector('.dashboard-shell');if(shell){shell.classList.toggle('sidebar-is-collapsed');}})(this)" onclick="(function(){var s=document.querySelector('.dashboard-sidebar');var sh=document.querySelector('.dashboard-shell');if(s&&sh){s.classList.toggle('!w-16');sh.classList.toggle('sidebar-is-collapsed');}})()"
type="button" type="button"
> >
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" width="16" height="16"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/></svg>
<path d="m15 18-6-6 6-6"/>
</svg>
</button> </button>
</div> </div>
<!-- Primary nav -->
<!-- GENERAL section --> <nav class="flex flex-1 flex-col" aria-label="Primary navigation">
<div class="sidebar-primary"> <ul role="list" class="grid py-3">
<div class="sidebar-section-label">General</div>
<ul class="sidebar-list" role="list">
for _, item := range sidebarPrimaryNavItems(activePath) { for _, item := range sidebarPrimaryNavItems(activePath) {
if item.DividerBefore {
<li class="my-2"><hr role="separator" class="border-gray-200"/></li>
}
<li> <li>
@SidebarNavItemRow(item) @SidebarNavItemRow(item)
</li> </li>
} }
</ul> </ul>
<!-- Projects section -->
<!-- PROJECTS section (tablo list) --> <div class="px-2 pb-2">
<div class="sidebar-projects"> <hr role="separator" class="border-gray-200 mb-3"/>
<div class="sidebar-section-label">Projects</div> <div class="px-2 mb-2">
<ul class="sidebar-project-list"> <span class="text-[10px] font-semibold text-gray-500 uppercase tracking-wider">Projets</span>
</div>
<ul class="space-y-0.5">
for _, tablo := range tablos { for _, tablo := range tablos {
<li> <li>
<a href={ templ.SafeURL("/tablos/" + tablo.ID.String()) } class="sidebar-project-link"> <a class="flex items-center gap-2.5 px-2 py-1.5 rounded-lg text-sm transition-colors text-gray-500 hover:bg-gray-100 hover:text-gray-900" href={ templ.SafeURL("/tablos/" + tablo.ID.String()) }>
if tablo.Color.Valid && tablo.Color.String != "" { <span class="w-6 h-6 rounded-full shrink-0 flex items-center justify-center border border-gray-300">
<span class="sidebar-project-icon" style={ "background-color: " + tablo.Color.String }></span> if tablo.Color.Valid && tablo.Color.String != "" {
} else { <span class="w-3 h-3 rounded-full" style={ "background-color: " + tablo.Color.String }></span>
<span class="sidebar-project-icon"></span> } else {
} <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-3.5 h-3.5 text-gray-500" aria-hidden="true"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>
<span class="sidebar-project-label">{ tablo.Title }</span> }
</span>
<span class="truncate flex-1">{ tablo.Title }</span>
</a> </a>
</li> </li>
} }
</ul> </ul>
</div> </div>
<!-- Feedback + separator at bottom -->
<ul role="list" class="mt-auto grid py-1">
<li>
<div class="flex w-full px-2.5 py-2 rounded-md cursor-pointer hover:bg-gray-100 font-medium">
<a class="w-full" href="#">
<div class="flex items-center gap-x-2.5 pl-2">
<span class="[&>svg]:w-5 [&>svg]:h-5 text-gray-500">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>
</span>
<div class="text-base font-normal text-gray-500">Feedback</div>
</div>
</a>
</div>
</li>
<li class="my-2"><hr role="separator" class="border-gray-200"/></li>
</ul>
</nav>
<!-- Org footer -->
<div class="flex flex-col px-1 pb-1.5 w-full gap-1">
<button class="flex items-center justify-start hover:bg-gray-100 w-full h-auto pl-2 py-1.5 gap-1 rounded-md text-sm" aria-label="User menu" type="button">
<span class="relative flex shrink-0 overflow-hidden rounded-full size-7 bg-purple-100 items-center justify-center">
<span class="text-[#804EEC] font-semibold text-sm">{ string([]rune(user.Email)[:1]) }</span>
</span>
<div class="flex flex-col items-start ml-1">
<p class="text-sm text-gray-700 font-medium truncate max-w-[7rem]">{ user.Email }</p>
<p class="text-xs text-gray-500">1 membre</p>
</div>
</button>
</div> </div>
</nav> </nav>
</aside> </aside>
} }
// PageHeader renders the full-width top bar with three zones: // PageHeader renders the top bar matching the production design:
// left (breadcrumb), center (search placeholder), right (bell, inbox, avatar dropdown). // left (search input), right (bell placeholder + avatar dropdown).
// Breadcrumb data is retained in AppLayout params for downstream phases but not shown visually.
// The avatar dropdown uses native HTML details/summary — no Alpine.js (D-06). // The avatar dropdown uses native HTML details/summary — no Alpine.js (D-06).
templ PageHeader(pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component, user *auth.User, csrfToken string) { templ PageHeader(pageTitle string, breadcrumb []BreadcrumbItem, headerActions templ.Component, user *auth.User, csrfToken string) {
<header class="page-header"> <header class="page-header h-[75px] flex items-center justify-between px-6 gap-4 border-b border-[#EAECF0] bg-white shrink-0">
<!-- LEFT: breadcrumb zone --> <!-- Hidden breadcrumb for screen readers and test assertions -->
<div class="page-header-left"> <nav class="breadcrumb sr-only" aria-label="Breadcrumb">
<nav class="breadcrumb" aria-label="Breadcrumb"> for i, crumb := range breadcrumb {
for i, crumb := range breadcrumb { if i > 0 {
if i > 0 { <span aria-hidden="true">/</span>
<span class="breadcrumb-separator" aria-hidden="true">/</span>
}
if crumb.Href != "" {
<a href={ templ.SafeURL(crumb.Href) } class="breadcrumb-item breadcrumb-item--link">{ crumb.Label }</a>
} else {
<span class="breadcrumb-item breadcrumb-item--current" aria-current="page">{ crumb.Label }</span>
}
} }
if len(breadcrumb) == 0 { if crumb.Href != "" {
<span class="breadcrumb-item breadcrumb-item--current">{ pageTitle }</span> <a href={ templ.SafeURL(crumb.Href) }>{ crumb.Label }</a>
} else {
<span aria-current="page">{ crumb.Label }</span>
} }
</nav> }
if len(breadcrumb) == 0 {
<span aria-current="page">{ pageTitle }</span>
}
</nav>
<!-- LEFT: search input -->
<div class="relative flex-1 max-w-sm">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</svg>
<input
placeholder="Search..."
class="w-full pl-9 pr-4 py-2 bg-transparent border border-[#EAECF0] rounded-lg text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500"
type="text"
disabled
/>
</div> </div>
<!-- RIGHT: bell + avatar -->
<!-- CENTER: search placeholder --> <div class="flex items-center gap-3">
<div class="page-header-center">
<div class="header-search-placeholder" aria-label="Search (coming soon)" role="search">
<svg class="header-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" width="16" height="16">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<span class="header-search-text">Search...</span>
</div>
</div>
<!-- RIGHT: bell, inbox, avatar -->
<div class="page-header-right">
<!-- Bell placeholder --> <!-- Bell placeholder -->
<button class="header-icon-button" aria-label="Notifications (coming soon)" type="button" disabled> <button
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" width="18" height="18"> class="relative w-10 h-10 border border-[#EAECF0] rounded-[8px] text-[#0C111D] hover:bg-gray-100 flex items-center justify-center"
aria-label="Notifications (coming soon)"
type="button"
disabled
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5" aria-hidden="true">
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path> <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path>
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path> <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path>
</svg> </svg>
</button> </button>
<!-- Avatar dropdown (details/summary, D-06) -->
<!-- Inbox placeholder --> <details class="header-avatar-menu relative">
<button class="header-icon-button" aria-label="Inbox (coming soon)" type="button" disabled> <summary class="flex items-center gap-2 p-1 hover:bg-gray-100 rounded-[8px] cursor-pointer list-none" aria-label="User menu">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" width="18" height="18"> <span class="relative flex size-10 shrink-0 overflow-hidden rounded-full bg-purple-100 items-center justify-center">
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline> <span class="text-[#804EEC] font-semibold text-sm">{ string([]rune(user.Email)[:1]) }</span>
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path> </span>
</svg>
</button>
<!-- Avatar dropdown using native details/summary (D-06) -->
<details class="header-avatar-menu">
<summary class="header-avatar-button" aria-label="User menu">
<span class="header-avatar-initial">{ string([]rune(user.Email)[:1]) }</span>
</summary> </summary>
<div class="header-avatar-dropdown"> <div class="header-avatar-dropdown absolute right-0 top-full mt-1 w-56 rounded-lg border border-gray-200 bg-white shadow-lg z-50 py-1">
<!-- Workspace / user info --> <div class="px-3 py-2">
<div class="avatar-dropdown-workspace"> <p class="text-sm font-medium text-gray-900 truncate">{ user.Email }</p>
<span class="avatar-dropdown-workspace-name">{ user.Email }</span> <p class="text-xs text-gray-500">1 membre</p>
<span class="avatar-dropdown-workspace-meta">1 member</span>
</div> </div>
<hr class="avatar-dropdown-divider"/> <hr class="border-gray-100"/>
<!-- Settings --> <a href="/settings" class="block px-3 py-2 text-sm text-gray-700 hover:bg-gray-50">Paramètres</a>
<a href="/settings" class="avatar-dropdown-item">Settings</a> <hr class="border-gray-100"/>
<hr class="avatar-dropdown-divider"/>
<!-- Logout -->
<form method="POST" action="/logout" class="avatar-dropdown-logout-form"> <form method="POST" action="/logout" class="avatar-dropdown-logout-form">
@ui.CSRFField(csrfToken) @ui.CSRFField(csrfToken)
<button type="submit" class="avatar-dropdown-item avatar-dropdown-item--danger">Log out</button> <button type="submit" class="w-full text-left px-3 py-2 text-sm text-red-600 hover:bg-gray-50">Se déconnecter</button>
</form> </form>
</div> </div>
</details> </details>
</div> </div>
<!-- Per-page header actions slot -->
<!-- Per-page header actions slot (Phase 19-22 will populate) -->
if headerActions != nil { if headerActions != nil {
<div class="page-header-actions"> <div class="flex items-center gap-2">
@headerActions @headerActions
</div> </div>
} }
@ -288,7 +311,9 @@ templ AppLayout(title string, user *auth.User, csrfToken string, activePath stri
@DashboardSidebar(activePath, tablos, user, csrfToken) @DashboardSidebar(activePath, tablos, user, csrfToken)
<main id="app-main-content" class="dashboard-main"> <main id="app-main-content" class="dashboard-main">
@PageHeader(pageTitle, breadcrumb, headerActions, user, csrfToken) @PageHeader(pageTitle, breadcrumb, headerActions, user, csrfToken)
{ children... } <div class="dashboard-main-content">
{ children... }
</div>
</main> </main>
</div> </div>
<script src="/static/htmx.min.js" defer></script> <script src="/static/htmx.min.js" defer></script>

View file

@ -11,53 +11,51 @@ type BreadcrumbItem struct {
// sidebarNavItem describes one entry in the sidebar primary navigation list. // sidebarNavItem describes one entry in the sidebar primary navigation list.
type sidebarNavItem struct { type sidebarNavItem struct {
Href string Href string
Label string Label string
Icon string Icon string
Active bool Active bool
DividerAfter bool DividerBefore bool
} }
// sidebarNavItemClass returns the CSS class string for a nav item. // navItemWrapperClass returns the Tailwind class string for a nav item wrapper.
// Active items receive "sidebar-nav-item is-active"; inactive receive "sidebar-nav-item". func navItemWrapperClass(active bool) string {
func sidebarNavItemClass(active bool) string {
if active { if active {
return "sidebar-nav-item is-active" return "flex w-full px-2.5 py-2 rounded-md cursor-pointer bg-purple-100 font-semibold"
} }
return "sidebar-nav-item" return "flex w-full px-2.5 py-2 rounded-md cursor-pointer hover:bg-gray-100 font-medium"
}
// navItemLabelClass returns the Tailwind class string for a nav item text label.
func navItemLabelClass(active bool) string {
if active {
return "text-base font-normal text-[#804EEC]"
}
return "text-base font-normal text-gray-500"
}
// navItemIconClass returns the Tailwind class string for a nav item icon wrapper.
func navItemIconClass(active bool) string {
if active {
return "[&>svg]:w-5 [&>svg]:h-5 text-[#804EEC]"
}
return "[&>svg]:w-5 [&>svg]:h-5 text-gray-500"
} }
// isActivePath reports whether activePath matches href. // isActivePath reports whether activePath matches href.
// Returns false when activePath is empty or blank.
func isActivePath(activePath string, href string) bool { func isActivePath(activePath string, href string) bool {
return strings.TrimSpace(activePath) != "" && activePath == href return strings.TrimSpace(activePath) != "" && activePath == href
} }
// sidebarNavItemID returns a stable HTML id attribute for the given nav href. // sidebarPrimaryNavItems returns the ordered sidebar nav items.
func sidebarNavItemID(href string) string { // French labels match the production app. DividerBefore adds an <hr> separator before the item.
switch href {
case "/":
return "sidebar-nav-home"
default:
slug := strings.Trim(strings.ReplaceAll(href, "/", "-"), "-")
if slug == "" {
slug = "item"
}
return "sidebar-nav-" + slug
}
}
// sidebarPrimaryNavItems returns the ordered list of primary nav items with
// active state computed from activePath.
//
// Per D-08 (Phase 18): GENERAL section items — Home, My Tasks, Projects, Events, Team Members.
// Chat and Files are removed from primary nav (accessible within tablo detail pages).
func sidebarPrimaryNavItems(activePath string) []sidebarNavItem { func sidebarPrimaryNavItems(activePath string) []sidebarNavItem {
return []sidebarNavItem{ return []sidebarNavItem{
{Href: "/", Label: "Home", Icon: "panels", Active: isActivePath(activePath, "/")}, {Href: "/", Label: "Aperçu", Icon: "panels", Active: isActivePath(activePath, "/")},
{Href: "#", Label: "My Tasks", Icon: "tasks", Active: false}, {Href: "#", Label: "Tâches", Icon: "tasks", Active: false, DividerBefore: true},
{Href: "/", Label: "Projects", Icon: "layers", Active: false}, {Href: "/", Label: "Projets", Icon: "layers", Active: false},
{Href: "/planning", Label: "Events", Icon: "planning", Active: isActivePath(activePath, "/planning")}, {Href: "/planning", Label: "Planning", Icon: "planning", Active: isActivePath(activePath, "/planning"), DividerBefore: true},
{Href: "#", Label: "Team Members", Icon: "team", Active: false}, {Href: "#", Label: "Discussions", Icon: "chat", Active: false},
{Href: "#", Label: "Fichiers", Icon: "files", Active: false},
} }
} }