From 9b0d335329d1698b165abfd1ebb4b2f8944c2925 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sat, 16 May 2026 21:43:15 +0200 Subject: [PATCH] feat(15-02): create app_layout.templ and app_layout_helpers.go with sidebar sub-components - Create backend/templates/app_layout_helpers.go: sidebarNavItem struct, sidebarNavItemClass, isActivePath, sidebarNavItemID, sidebarPrimaryNavItems - Create backend/templates/app_layout.templ: SidebarNavIcon, SidebarNavItemRow, SidebarProjectsSection, SidebarOrganizationFooter, DashboardSidebar, AppLayout - templ generate and go build exit 0; all existing tests pass --- backend/templates/app_layout.templ | 174 ++++++++++++++++++++++++ backend/templates/app_layout_helpers.go | 55 ++++++++ 2 files changed, 229 insertions(+) create mode 100644 backend/templates/app_layout.templ create mode 100644 backend/templates/app_layout_helpers.go diff --git a/backend/templates/app_layout.templ b/backend/templates/app_layout.templ new file mode 100644 index 0000000..1205d8b --- /dev/null +++ b/backend/templates/app_layout.templ @@ -0,0 +1,174 @@ +package templates + +import ( + "backend/internal/auth" + "backend/internal/db/sqlc" + "backend/internal/web/ui" +) + +// SidebarNavIcon renders the SVG icon for the given sidebar icon kind. +// SVG paths ported verbatim from go-backend/internal/web/views/icons.templ SidebarIcon. +templ SidebarNavIcon(kind string) { + switch kind { + case "panels": + + case "tasks": + + case "layers": + + case "planning": + + case "chat": + + case "files": + + default: + + } +} + +// SidebarNavItemRow renders one nav list item. +// 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) { + if item.Href == "" { +
+ +
+ } else { + + + + } +} + +// SidebarProjectsSection renders the projects list in the sidebar. +templ SidebarProjectsSection(tablos []sqlc.Tablo) { + +} + +// SidebarOrganizationFooter renders the user/org info at the bottom of the sidebar. +// Per D-F01/D-F02/D-F03: shows user email initial as avatar, email as name, and logout form. +templ SidebarOrganizationFooter(user *auth.User, csrfToken string) { + +} + +// DashboardSidebar renders the full sidebar with brand, nav, projects, and footer. +templ DashboardSidebar(activePath string, tablos []sqlc.Tablo, user *auth.User, csrfToken string) { + +} + +// AppLayout is the authenticated HTML shell for all dashboard pages. +// It replaces Layout for all authenticated routes in Phase 15+. +// +// Differences from Layout: +// - Uses .dashboard-shell grid with sidebar + main content +// - DashboardSidebar renders nav, projects, and org footer +// - main#app-main-content.dashboard-main wraps page children +// - Scripts: htmx.min.js, sortable.min.js, discussion-sse.js (per D-L04) +templ AppLayout(title string, user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo) { + + + + + + { title } + + + +
+ @DashboardSidebar(activePath, tablos, user, csrfToken) +
+ { children... } +
+
+ + + + + +} diff --git a/backend/templates/app_layout_helpers.go b/backend/templates/app_layout_helpers.go new file mode 100644 index 0000000..87b5da1 --- /dev/null +++ b/backend/templates/app_layout_helpers.go @@ -0,0 +1,55 @@ +package templates + +import "strings" + +// sidebarNavItem describes one entry in the sidebar primary navigation list. +type sidebarNavItem struct { + Href string + Label string + Icon string + Active bool + DividerAfter bool +} + +// sidebarNavItemClass returns the CSS class string for a nav item. +// Active items receive "sidebar-nav-item is-active"; inactive receive "sidebar-nav-item". +func sidebarNavItemClass(active bool) string { + if active { + return "sidebar-nav-item is-active" + } + return "sidebar-nav-item" +} + +// isActivePath reports whether activePath matches href. +// Returns false when activePath is empty or blank. +func isActivePath(activePath string, href string) bool { + return strings.TrimSpace(activePath) != "" && activePath == href +} + +// sidebarNavItemID returns a stable HTML id attribute for the given nav href. +func sidebarNavItemID(href string) string { + 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-N01/D-N02: Tasks, Chat, and Files are visual-only (no Href) in Phase 15. +func sidebarPrimaryNavItems(activePath string) []sidebarNavItem { + return []sidebarNavItem{ + {Href: "/", Label: "Dashboard", Icon: "panels", Active: isActivePath(activePath, "/"), DividerAfter: true}, + {Href: "", Label: "Tasks", Icon: "tasks", Active: false}, + {Href: "/planning", Label: "Planning", Icon: "planning", Active: isActivePath(activePath, "/planning")}, + {Href: "", Label: "Chat", Icon: "chat", Active: false}, + {Href: "", Label: "Files", Icon: "files", Active: false}, + } +}