From f835fc7c511d6ac188197b065253603510e91dcc Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Sun, 11 Jan 2026 12:41:54 -0500 Subject: [PATCH] feat: Initial navbar --- Public/css/output.css | 51 +++++ Sources/ViewController/Views/Navbar.swift | 49 +++++ .../Views/Project/ProjectView.swift | 187 ++++++------------ .../Views/Project/ProjectsTable.swift | 51 ++--- 4 files changed, 182 insertions(+), 156 deletions(-) create mode 100644 Sources/ViewController/Views/Navbar.swift diff --git a/Public/css/output.css b/Public/css/output.css index 2c4275c..0858895 100644 --- a/Public/css/output.css +++ b/Public/css/output.css @@ -5363,6 +5363,9 @@ } } } + .my-1 { + margin-block: calc(var(--spacing) * 1); + } .my-1\.5 { margin-block: calc(var(--spacing) * 1.5); } @@ -5702,6 +5705,9 @@ font-weight: 600; } } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } .mb-6 { margin-bottom: calc(var(--spacing) * 6); } @@ -6561,9 +6567,27 @@ width: calc(var(--size-selector, 0.25rem) * 4); } } + .w-24 { + width: calc(var(--spacing) * 24); + } + .w-64 { + width: calc(var(--spacing) * 64); + } + .w-\[80px\] { + width: 80px; + } + .w-fit { + width: fit-content; + } .w-full { width: 100%; } + .flex-1 { + flex: 1; + } + .flex-none { + flex: none; + } .flex-shrink { flex-shrink: 1; } @@ -6928,6 +6952,9 @@ .rounded-md { border-radius: var(--radius-md); } + .rounded-none { + border-radius: 0; + } .rounded-selector { border-radius: var(--radius-selector); } @@ -7392,6 +7419,12 @@ .bg-base-300 { background-color: var(--color-base-300); } + .bg-neutral { + background-color: var(--color-neutral); + } + .bg-primary { + background-color: var(--color-primary); + } .bg-red-500 { background-color: var(--color-red-500); } @@ -7809,6 +7842,9 @@ .px-4 { padding-inline: calc(var(--spacing) * 4); } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } .py-1\.5 { padding-block: calc(var(--spacing) * 1.5); } @@ -7835,6 +7871,9 @@ .pe-2 { padding-inline-end: calc(var(--spacing) * 2); } + .pe-4 { + padding-inline-end: calc(var(--spacing) * 4); + } .pt-6 { padding-top: calc(var(--spacing) * 6); } @@ -8462,6 +8501,9 @@ color: var(--color-warning); } } + .text-base-content { + color: var(--color-base-content); + } .text-error { color: var(--color-error); } @@ -9748,6 +9790,15 @@ justify-content: flex-start; } } + .is-drawer-open\:space-x-2 { + &:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); + } + } + } .is-drawer-open\:space-x-4 { &:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) { :where(& > :not(:last-child)) { diff --git a/Sources/ViewController/Views/Navbar.swift b/Sources/ViewController/Views/Navbar.swift new file mode 100644 index 0000000..e5f163f --- /dev/null +++ b/Sources/ViewController/Views/Navbar.swift @@ -0,0 +1,49 @@ +import Elementary +import ManualDCore +import Styleguide + +struct Navbar: HTML, Sendable { + let sidebarToggle: Bool + + var body: some HTML { + nav(.class("navbar w-full bg-base-300 text-base-content shadow-sm mb-4")) { + div(.class("flex-1 space-x-4 items-center")) { + if sidebarToggle { + Tooltip("Open sidebar", position: .right) { + label( + .for("my-drawer-1"), + .class("size-7"), + .init(name: "aria-label", value: "open sidebar") + ) { + SVG(.sidebarToggle) + } + .navButton() + } + } + Tooltip("Home", position: .right) { + a( + .class("w-fit text-xl py-2 px-4"), + .href(route: .project(.index)) + ) { + "Manual-D" + } + .navButton() + } + } + div(.class("flex-none")) { + button(.class("w-fit px-4 py-2")) { + "User Menu" + } + .navButton() + } + } + } +} + +extension HTML where Tag: HTMLTrait.Attributes.Global { + func navButton() -> _AttributedElement { + attributes( + .class("btn btn-square btn-ghost hover:bg-neutral hover:text-white") + ) + } +} diff --git a/Sources/ViewController/Views/Project/ProjectView.swift b/Sources/ViewController/Views/Project/ProjectView.swift index 2edba36..5a2b220 100644 --- a/Sources/ViewController/Views/Project/ProjectView.swift +++ b/Sources/ViewController/Views/Project/ProjectView.swift @@ -35,69 +35,64 @@ struct ProjectView: HTML, Sendable { div(.class("drawer lg:drawer-open")) { input(.id("my-drawer-1"), .type(.checkbox), .class("drawer-toggle")) - div(.class("drawer-content p-4")) { - Tooltip("Open sidebar", position: .right) { - label( - .for("my-drawer-1"), - .class("btn btn-square btn-ghost drawer-button size-7 pb-6") - ) { - SVG(.sidebarToggle) - } - } - switch self.activeTab { - case .project: - await resultView(projectID) { - guard let project = try await database.projects.get(projectID) else { - throw NotFoundError() + div(.class("drawer-content")) { + Navbar(sidebarToggle: true) + div(.class("p-4")) { + switch self.activeTab { + case .project: + await resultView(projectID) { + guard let project = try await database.projects.get(projectID) else { + throw NotFoundError() + } + return project + } onSuccess: { project in + ProjectDetail(project: project) + } + case .rooms: + await resultView(projectID) { + try await ( + database.rooms.fetch(projectID), + database.projects.getSensibleHeatRatio(projectID) + ) + } onSuccess: { (rooms, shr) in + RoomsView(rooms: rooms, sensibleHeatRatio: shr) } - return project - } onSuccess: { project in - ProjectDetail(project: project) - } - case .rooms: - await resultView(projectID) { - try await ( - database.rooms.fetch(projectID), - database.projects.getSensibleHeatRatio(projectID) - ) - } onSuccess: { (rooms, shr) in - RoomsView(rooms: rooms, sensibleHeatRatio: shr) - } - case .equivalentLength: - await resultView(projectID) { - try await database.effectiveLength.fetch(projectID) - } onSuccess: { - EffectiveLengthsView(effectiveLengths: $0) - } - case .frictionRate: + case .equivalentLength: + await resultView(projectID) { + try await database.effectiveLength.fetch(projectID) + } onSuccess: { + EffectiveLengthsView(effectiveLengths: $0) + } + case .frictionRate: - await resultView(projectID) { + await resultView(projectID) { - let equipmentInfo = try await database.equipment.fetch(projectID) - let componentLosses = try await database.componentLoss.fetch(projectID) - let equivalentLengths = try await database.effectiveLength.fetchMax(projectID) - let frictionRateResponse = try await manualD.frictionRate( - equipmentInfo: equipmentInfo, - componentLosses: componentLosses, - effectiveLength: equivalentLengths - ) - return ( - equipmentInfo, componentLosses, equivalentLengths, frictionRateResponse - ) - } onSuccess: { - FrictionRateView( - equipmentInfo: $0.0, - componentLosses: $0.1, - equivalentLengths: $0.2, - frictionRateResponse: $0.3 - ) - } - case .ductSizing: - await resultView(projectID) { - try await database.calculateDuctSizes(projectID: projectID) - } onSuccess: { - DuctSizingView(rooms: $0) + let equipmentInfo = try await database.equipment.fetch(projectID) + let componentLosses = try await database.componentLoss.fetch(projectID) + let equivalentLengths = try await database.effectiveLength.fetchMax(projectID) + let frictionRateResponse = try await manualD.frictionRate( + equipmentInfo: equipmentInfo, + componentLosses: componentLosses, + effectiveLength: equivalentLengths + ) + return ( + equipmentInfo, componentLosses, equivalentLengths, frictionRateResponse + ) + } onSuccess: { + FrictionRateView( + equipmentInfo: $0.0, + componentLosses: $0.1, + equivalentLengths: $0.2, + frictionRateResponse: $0.3 + ) + } + case .ductSizing: + await resultView(projectID) { + try await database.calculateDuctSizes(projectID: projectID) + } onSuccess: { + DuctSizingView(rooms: $0) + } } } } @@ -148,7 +143,7 @@ extension ProjectView { div( .class( """ - flex min-h-full flex-col items-start bg-base-200 + flex min-h-full flex-col items-start bg-base-300 text-base-content is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px] """ ) @@ -156,32 +151,6 @@ extension ProjectView { ul(.class("w-full")) { - li(.class("w-full")) { - div( - .class("w-full is-drawer-close:tooltip is-drawer-close:tooltip-right"), - .data("tip", value: "All Projects") - ) { - a( - .class( - """ - flex btn btn-secondary btn-square btn-block - items-center - """ - ), - .hx.get(route: .project(.index)), - .hx.target("body"), - .hx.pushURL(true), - .hx.swap(.outerHTML), - ) { - div(.class("flex is-drawer-open:space-x-4")) { - // span { "<" } - SVG(.chevronsLeft) - span(.class("is-drawer-close:hidden")) { "All Projects" } - } - } - } - } - // FIX: Move to user profile / settings page. li(.class("w-full is-drawer-close:hidden")) { div(.class("flex justify-between p-4")) { @@ -198,7 +167,6 @@ extension ProjectView { isComplete: true ) .attributes(.data("active", value: "true"), when: active == .project) - .attributes(.class("btn-active"), when: active == .project) } li(.class("w-full")) { @@ -208,7 +176,6 @@ extension ProjectView { route: .project(.detail(projectID, .rooms(.index))), isComplete: completedSteps.rooms ) - .attributes(.class("btn-active"), when: active == .rooms) .attributes(.data("active", value: "true"), when: active == .rooms) } @@ -221,7 +188,6 @@ extension ProjectView { isComplete: completedSteps.equivalentLength ) .attributes(.data("active", value: "true"), when: active == .equivalentLength) - .attributes(.class("btn-active"), when: active == .equivalentLength) // } } @@ -233,7 +199,6 @@ extension ProjectView { isComplete: completedSteps.frictionRate ) .attributes(.data("active", value: "true"), when: active == .frictionRate) - .attributes(.class("btn-active"), when: active == .frictionRate) } li(.class("w-full")) { @@ -245,7 +210,6 @@ extension ProjectView { hideIsComplete: true ) .attributes(.data("active", value: "true"), when: active == .ductSizing) - .attributes(.class("btn-active"), when: active == .ductSizing) } } } @@ -316,47 +280,6 @@ extension ProjectView { } } } - // a( - // - // // flex w-full btn btn-soft btn-square btn-block btn-lg - // .class( - // """ - // flex w-full hover:bg-gray-900 data-active:bg-gray-900 - // is-drawer-open:justify-between is-drawer-close:items-center - // is-drawer-close:tooltip is-drawer-close:tooltip-right - // is-drawer-close:justify-center - // """ - // ), - // .href(href), - // .data("tip", value: title) - // ) { - // div( - // .class( - // """ - // justify-center items-center mx-auto space-4 py-2 - // is-drawer-open:flex is-drawer-open:space-x-4 - // """ - // ) - // ) { - // SVG(icon) - // span(.class("text-gray-200 is-drawer-open:text-xl is-drawer-close:text-sm")) { - // title - // } - // } - // if !hideIsComplete { - // div(.class("is-drawer-close:hidden")) { - // if isComplete { - // SVG(.badgeCheck) - // } else { - // SVG(.ban) - // } - // } - // .attributes(.class("text-green-400"), when: isComplete) - // .attributes(.class("text-error"), when: !isComplete) - // } - // } - // .attributes(.class("is-drawer-close:text-green-400"), when: isComplete) - // .attributes(.class("is-drawer-close:text-error"), when: !isComplete && !hideIsComplete) } private func row( diff --git a/Sources/ViewController/Views/Project/ProjectsTable.swift b/Sources/ViewController/Views/Project/ProjectsTable.swift index 4af3e42..c026dd0 100644 --- a/Sources/ViewController/Views/Project/ProjectsTable.swift +++ b/Sources/ViewController/Views/Project/ProjectsTable.swift @@ -16,36 +16,39 @@ struct ProjectsTable: HTML, Sendable { } var body: some HTML { - div(.class("m-6")) { - Row { - h1(.class("text-2xl font-bold")) { "Projects" } - Tooltip("Add project") { - PlusButton() - .attributes( - .class("btn-ghost"), - .showModal(id: ProjectForm.id) - ) + div { + Navbar(sidebarToggle: false) + div(.class("m-6")) { + Row { + h1(.class("text-2xl font-bold")) { "Projects" } + Tooltip("Add project") { + PlusButton() + .attributes( + .class("btn-ghost"), + .showModal(id: ProjectForm.id) + ) + } } - } - .attributes(.class("pb-6")) + .attributes(.class("pb-6")) - div(.class("overflow-x-auto rounded-box border")) { - table(.class("table table-zebra")) { - thead { - tr { - th { Label("Date") } - th { Label("Name") } - th { Label("Address") } - th {} + div(.class("overflow-x-auto rounded-box border")) { + table(.class("table table-zebra")) { + thead { + tr { + th { Label("Date") } + th { Label("Name") } + th { Label("Address") } + th {} + } + } + tbody { + Rows(projects: projects) } } - tbody { - Rows(projects: projects) - } } - } - ProjectForm(dismiss: true) + ProjectForm(dismiss: true) + } } } }