feat: Initial navbar

This commit is contained in:
2026-01-11 12:41:54 -05:00
parent 51edff5a8a
commit f835fc7c51
4 changed files with 182 additions and 156 deletions

View File

@@ -5363,6 +5363,9 @@
} }
} }
} }
.my-1 {
margin-block: calc(var(--spacing) * 1);
}
.my-1\.5 { .my-1\.5 {
margin-block: calc(var(--spacing) * 1.5); margin-block: calc(var(--spacing) * 1.5);
} }
@@ -5702,6 +5705,9 @@
font-weight: 600; font-weight: 600;
} }
} }
.mb-4 {
margin-bottom: calc(var(--spacing) * 4);
}
.mb-6 { .mb-6 {
margin-bottom: calc(var(--spacing) * 6); margin-bottom: calc(var(--spacing) * 6);
} }
@@ -6561,9 +6567,27 @@
width: calc(var(--size-selector, 0.25rem) * 4); 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 { .w-full {
width: 100%; width: 100%;
} }
.flex-1 {
flex: 1;
}
.flex-none {
flex: none;
}
.flex-shrink { .flex-shrink {
flex-shrink: 1; flex-shrink: 1;
} }
@@ -6928,6 +6952,9 @@
.rounded-md { .rounded-md {
border-radius: var(--radius-md); border-radius: var(--radius-md);
} }
.rounded-none {
border-radius: 0;
}
.rounded-selector { .rounded-selector {
border-radius: var(--radius-selector); border-radius: var(--radius-selector);
} }
@@ -7392,6 +7419,12 @@
.bg-base-300 { .bg-base-300 {
background-color: var(--color-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 { .bg-red-500 {
background-color: var(--color-red-500); background-color: var(--color-red-500);
} }
@@ -7809,6 +7842,9 @@
.px-4 { .px-4 {
padding-inline: calc(var(--spacing) * 4); padding-inline: calc(var(--spacing) * 4);
} }
.py-1 {
padding-block: calc(var(--spacing) * 1);
}
.py-1\.5 { .py-1\.5 {
padding-block: calc(var(--spacing) * 1.5); padding-block: calc(var(--spacing) * 1.5);
} }
@@ -7835,6 +7871,9 @@
.pe-2 { .pe-2 {
padding-inline-end: calc(var(--spacing) * 2); padding-inline-end: calc(var(--spacing) * 2);
} }
.pe-4 {
padding-inline-end: calc(var(--spacing) * 4);
}
.pt-6 { .pt-6 {
padding-top: calc(var(--spacing) * 6); padding-top: calc(var(--spacing) * 6);
} }
@@ -8462,6 +8501,9 @@
color: var(--color-warning); color: var(--color-warning);
} }
} }
.text-base-content {
color: var(--color-base-content);
}
.text-error { .text-error {
color: var(--color-error); color: var(--color-error);
} }
@@ -9748,6 +9790,15 @@
justify-content: flex-start; 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 { .is-drawer-open\:space-x-4 {
&:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) { &:where(.drawer-toggle:checked ~ .drawer-side, .drawer-toggle:checked ~ .drawer-side *) {
:where(& > :not(:last-child)) { :where(& > :not(:last-child)) {

View File

@@ -0,0 +1,49 @@
import Elementary
import ManualDCore
import Styleguide
struct Navbar: HTML, Sendable {
let sidebarToggle: Bool
var body: some HTML<HTMLTag.nav> {
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<Self> {
attributes(
.class("btn btn-square btn-ghost hover:bg-neutral hover:text-white")
)
}
}

View File

@@ -35,69 +35,64 @@ struct ProjectView: HTML, Sendable {
div(.class("drawer lg:drawer-open")) { div(.class("drawer lg:drawer-open")) {
input(.id("my-drawer-1"), .type(.checkbox), .class("drawer-toggle")) input(.id("my-drawer-1"), .type(.checkbox), .class("drawer-toggle"))
div(.class("drawer-content p-4")) { div(.class("drawer-content")) {
Tooltip("Open sidebar", position: .right) { Navbar(sidebarToggle: true)
label( div(.class("p-4")) {
.for("my-drawer-1"), switch self.activeTab {
.class("btn btn-square btn-ghost drawer-button size-7 pb-6") case .project:
) { await resultView(projectID) {
SVG(.sidebarToggle) guard let project = try await database.projects.get(projectID) else {
} throw NotFoundError()
} }
switch self.activeTab { return project
case .project: } onSuccess: { project in
await resultView(projectID) { ProjectDetail(project: project)
guard let project = try await database.projects.get(projectID) else { }
throw NotFoundError() 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: case .equivalentLength:
await resultView(projectID) { await resultView(projectID) {
try await database.effectiveLength.fetch(projectID) try await database.effectiveLength.fetch(projectID)
} onSuccess: { } onSuccess: {
EffectiveLengthsView(effectiveLengths: $0) EffectiveLengthsView(effectiveLengths: $0)
} }
case .frictionRate: case .frictionRate:
await resultView(projectID) { await resultView(projectID) {
let equipmentInfo = try await database.equipment.fetch(projectID) let equipmentInfo = try await database.equipment.fetch(projectID)
let componentLosses = try await database.componentLoss.fetch(projectID) let componentLosses = try await database.componentLoss.fetch(projectID)
let equivalentLengths = try await database.effectiveLength.fetchMax(projectID) let equivalentLengths = try await database.effectiveLength.fetchMax(projectID)
let frictionRateResponse = try await manualD.frictionRate( let frictionRateResponse = try await manualD.frictionRate(
equipmentInfo: equipmentInfo, equipmentInfo: equipmentInfo,
componentLosses: componentLosses, componentLosses: componentLosses,
effectiveLength: equivalentLengths effectiveLength: equivalentLengths
) )
return ( return (
equipmentInfo, componentLosses, equivalentLengths, frictionRateResponse equipmentInfo, componentLosses, equivalentLengths, frictionRateResponse
) )
} onSuccess: { } onSuccess: {
FrictionRateView( FrictionRateView(
equipmentInfo: $0.0, equipmentInfo: $0.0,
componentLosses: $0.1, componentLosses: $0.1,
equivalentLengths: $0.2, equivalentLengths: $0.2,
frictionRateResponse: $0.3 frictionRateResponse: $0.3
) )
} }
case .ductSizing: case .ductSizing:
await resultView(projectID) { await resultView(projectID) {
try await database.calculateDuctSizes(projectID: projectID) try await database.calculateDuctSizes(projectID: projectID)
} onSuccess: { } onSuccess: {
DuctSizingView(rooms: $0) DuctSizingView(rooms: $0)
}
} }
} }
} }
@@ -148,7 +143,7 @@ extension ProjectView {
div( div(
.class( .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] is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]
""" """
) )
@@ -156,32 +151,6 @@ extension ProjectView {
ul(.class("w-full")) { 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. // FIX: Move to user profile / settings page.
li(.class("w-full is-drawer-close:hidden")) { li(.class("w-full is-drawer-close:hidden")) {
div(.class("flex justify-between p-4")) { div(.class("flex justify-between p-4")) {
@@ -198,7 +167,6 @@ extension ProjectView {
isComplete: true isComplete: true
) )
.attributes(.data("active", value: "true"), when: active == .project) .attributes(.data("active", value: "true"), when: active == .project)
.attributes(.class("btn-active"), when: active == .project)
} }
li(.class("w-full")) { li(.class("w-full")) {
@@ -208,7 +176,6 @@ extension ProjectView {
route: .project(.detail(projectID, .rooms(.index))), route: .project(.detail(projectID, .rooms(.index))),
isComplete: completedSteps.rooms isComplete: completedSteps.rooms
) )
.attributes(.class("btn-active"), when: active == .rooms)
.attributes(.data("active", value: "true"), when: active == .rooms) .attributes(.data("active", value: "true"), when: active == .rooms)
} }
@@ -221,7 +188,6 @@ extension ProjectView {
isComplete: completedSteps.equivalentLength isComplete: completedSteps.equivalentLength
) )
.attributes(.data("active", value: "true"), when: active == .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 isComplete: completedSteps.frictionRate
) )
.attributes(.data("active", value: "true"), when: active == .frictionRate) .attributes(.data("active", value: "true"), when: active == .frictionRate)
.attributes(.class("btn-active"), when: active == .frictionRate)
} }
li(.class("w-full")) { li(.class("w-full")) {
@@ -245,7 +210,6 @@ extension ProjectView {
hideIsComplete: true hideIsComplete: true
) )
.attributes(.data("active", value: "true"), when: active == .ductSizing) .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( private func row(

View File

@@ -16,36 +16,39 @@ struct ProjectsTable: HTML, Sendable {
} }
var body: some HTML { var body: some HTML {
div(.class("m-6")) { div {
Row { Navbar(sidebarToggle: false)
h1(.class("text-2xl font-bold")) { "Projects" } div(.class("m-6")) {
Tooltip("Add project") { Row {
PlusButton() h1(.class("text-2xl font-bold")) { "Projects" }
.attributes( Tooltip("Add project") {
.class("btn-ghost"), PlusButton()
.showModal(id: ProjectForm.id) .attributes(
) .class("btn-ghost"),
.showModal(id: ProjectForm.id)
)
}
} }
} .attributes(.class("pb-6"))
.attributes(.class("pb-6"))
div(.class("overflow-x-auto rounded-box border")) { div(.class("overflow-x-auto rounded-box border")) {
table(.class("table table-zebra")) { table(.class("table table-zebra")) {
thead { thead {
tr { tr {
th { Label("Date") } th { Label("Date") }
th { Label("Name") } th { Label("Name") }
th { Label("Address") } th { Label("Address") }
th {} th {}
}
}
tbody {
Rows(projects: projects)
} }
} }
tbody {
Rows(projects: projects)
}
} }
}
ProjectForm(dismiss: true) ProjectForm(dismiss: true)
}
} }
} }
} }