From 1b88f81b5f609e8695b87ecaaf639f258a89a7d6 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 14 Jan 2026 23:09:28 -0500 Subject: [PATCH] feat: Adds page header styles, starts an Alert component. --- Public/css/output.css | 42 ++++++++ Sources/Styleguide/Alert.swift | 28 +++++ Sources/Styleguide/Buttons.swift | 4 +- Sources/Styleguide/ElementaryExtensions.swift | 4 + Sources/Styleguide/PageTitle.swift | 22 ++++ Sources/Styleguide/SVG.swift | 5 + Sources/Styleguide/Tooltip.swift | 16 ++- .../ComponentLoss/ComponentLossesView.swift | 13 ++- .../Views/DuctSizing/DuctSizingView.swift | 54 +++++----- .../EffectiveLengthsView.swift | 5 +- .../EquipmentInfo/EquipmentInfoView.swift | 17 ++- .../Views/FrictionRate/FrictionRateView.swift | 101 ++++++++++-------- Sources/ViewController/Views/MainPage.swift | 2 +- .../Views/Project/ProjectDetail.swift | 8 +- .../Views/Rooms/RoomsView.swift | 91 ++++++++-------- 15 files changed, 272 insertions(+), 140 deletions(-) create mode 100644 Sources/Styleguide/Alert.swift diff --git a/Public/css/output.css b/Public/css/output.css index b558c0c..b5427b5 100644 --- a/Public/css/output.css +++ b/Public/css/output.css @@ -29,6 +29,7 @@ --text-3xl: 1.875rem; --text-3xl--line-height: calc(2.25 / 1.875); --font-weight-bold: 700; + --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --ease-out: cubic-bezier(0, 0, 0.2, 1); @@ -5379,6 +5380,9 @@ .my-6 { margin-block: calc(var(--spacing) * 6); } + .my-auto { + margin-block: auto; + } .label { @layer daisyui.l1.l2.l3 { display: inline-flex; @@ -6893,6 +6897,12 @@ margin-inline-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse))); } } + .gap-y-4 { + row-gap: calc(var(--spacing) * 4); + } + .gap-y-6 { + row-gap: calc(var(--spacing) * 6); + } .overflow-auto { overflow: auto; } @@ -7005,6 +7015,9 @@ .rounded-selector { border-radius: var(--radius-selector); } + .rounded-sm { + border-radius: var(--radius-sm); + } .rounded-t-box { border-top-left-radius: var(--radius-box); border-top-right-radius: var(--radius-box); @@ -7177,6 +7190,14 @@ border-style: var(--tw-border-style); border-width: 1px; } + .border-2 { + border-style: var(--tw-border-style); + border-width: 2px; + } + .border-y { + border-block-style: var(--tw-border-style); + border-block-width: 1px; + } .border-t { border-top-style: var(--tw-border-style); border-top-width: 1px; @@ -7307,6 +7328,12 @@ .border-gray-200 { border-color: var(--color-gray-200); } + .border-primary { + border-color: var(--color-primary); + } + .border-secondary { + border-color: var(--color-secondary); + } .menu-active { :where(:not(ul, details, .menu-title, .btn))& { @layer daisyui.l1.l2 { @@ -7467,9 +7494,15 @@ .bg-base-300 { background-color: var(--color-base-300); } + .bg-primary { + background-color: var(--color-primary); + } .bg-red-500 { background-color: var(--color-red-500); } + .bg-secondary { + background-color: var(--color-secondary); + } .bg-white { background-color: var(--color-white); } @@ -7761,6 +7794,9 @@ .p-4 { padding: calc(var(--spacing) * 4); } + .p-6 { + padding: calc(var(--spacing) * 6); + } .menu-title { @layer daisyui.l1.l2.l3 { padding-inline: calc(0.25rem * 3); @@ -8554,6 +8590,12 @@ .text-primary { color: var(--color-primary); } + .text-primary-content { + color: var(--color-primary-content); + } + .text-secondary-content { + color: var(--color-secondary-content); + } .text-slate-900 { color: var(--color-slate-900); } diff --git a/Sources/Styleguide/Alert.swift b/Sources/Styleguide/Alert.swift new file mode 100644 index 0000000..d507b77 --- /dev/null +++ b/Sources/Styleguide/Alert.swift @@ -0,0 +1,28 @@ +import Elementary + +public struct Alert: HTML { + + let inner: Content + + public init(@HTMLBuilder content: () -> Content) { + self.inner = content() + } + + public var body: some HTML { + div(.class("flex space-x-2")) { + SVG(.triangleAlert) + inner + } + } +} + +extension Alert: Sendable where Content: Sendable {} + +extension Alert where Content == p { + + public init(_ description: String) { + self.init { + p { description } + } + } +} diff --git a/Sources/Styleguide/Buttons.swift b/Sources/Styleguide/Buttons.swift index 36f6072..c5914d2 100644 --- a/Sources/Styleguide/Buttons.swift +++ b/Sources/Styleguide/Buttons.swift @@ -65,7 +65,7 @@ public struct EditButton: HTML, Sendable { } public var body: some HTML { - button(.class("btn hover:btn-success"), .type(type)) { + button(.class("btn"), .type(type)) { div(.class("flex")) { if let title { span(.class("pe-2")) { title } @@ -83,7 +83,7 @@ public struct PlusButton: HTML, Sendable { public var body: some HTML { button( .type(.button), - .class("btn btn-primary btn-circle text-xl") + .class("btn") ) { SVG(.circlePlus) } } } diff --git a/Sources/Styleguide/ElementaryExtensions.swift b/Sources/Styleguide/ElementaryExtensions.swift index c3fc01e..13d1c2d 100644 --- a/Sources/Styleguide/ElementaryExtensions.swift +++ b/Sources/Styleguide/ElementaryExtensions.swift @@ -45,4 +45,8 @@ extension HTML where Tag: HTMLTrait.Attributes.Global { public func badge() -> _AttributedElement { attributes(.class("badge badge-lg badge-outline font-bold")) } + + public func hidden(when shouldHide: Bool) -> _AttributedElement { + attributes(.class("hidden"), when: shouldHide) + } } diff --git a/Sources/Styleguide/PageTitle.swift b/Sources/Styleguide/PageTitle.swift index 9d42013..49ad93c 100644 --- a/Sources/Styleguide/PageTitle.swift +++ b/Sources/Styleguide/PageTitle.swift @@ -1,5 +1,27 @@ import Elementary +public struct PageTitleRow: HTML, Sendable where Content: Sendable { + + let inner: Content + + public init(@HTMLBuilder content: () -> Content) { + self.inner = content() + } + + public var body: some HTML { + div( + .class( + """ + flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm + p-6 w-full + """ + ) + ) { + inner + } + } +} + public struct PageTitle: HTML, Sendable { let title: String diff --git a/Sources/Styleguide/SVG.swift b/Sources/Styleguide/SVG.swift index eb4851d..be0a04e 100644 --- a/Sources/Styleguide/SVG.swift +++ b/Sources/Styleguide/SVG.swift @@ -33,6 +33,7 @@ extension SVG { case squareFunction case squarePen case trash + case triangleAlert case user case wind @@ -135,6 +136,10 @@ extension SVG { return """ """ + case .triangleAlert: + return """ + + """ case .user: return """ diff --git a/Sources/Styleguide/Tooltip.swift b/Sources/Styleguide/Tooltip.swift index 503c134..9b988f1 100644 --- a/Sources/Styleguide/Tooltip.swift +++ b/Sources/Styleguide/Tooltip.swift @@ -1,6 +1,18 @@ import Elementary -public struct Tooltip: HTML, Sendable { +extension HTML { + + public func tooltip( + _ tip: String, + position: TooltipPosition = .default + ) -> Tooltip { + Tooltip(tip, position: position) { + self + } + } +} + +public struct Tooltip: HTML { let tooltip: String let position: TooltipPosition @@ -26,6 +38,8 @@ public struct Tooltip: HTML, Sendable { } } +extension Tooltip: Sendable where Inner: Sendable {} + public enum TooltipPosition: String, CaseIterable, Sendable { public static let `default` = Self.left diff --git a/Sources/ViewController/Views/ComponentLoss/ComponentLossesView.swift b/Sources/ViewController/Views/ComponentLoss/ComponentLossesView.swift index 9d4139d..6146618 100644 --- a/Sources/ViewController/Views/ComponentLoss/ComponentLossesView.swift +++ b/Sources/ViewController/Views/ComponentLoss/ComponentLossesView.swift @@ -31,13 +31,12 @@ struct ComponentPressureLossesView: HTML, Sendable { th { "Value" } th { div(.class("flex justify-end mx-auto")) { - Tooltip("Add Component Loss") { - PlusButton() - .attributes( - .class("btn-ghost text-2xl me-2"), - .showModal(id: ComponentLossForm.id()) - ) - } + PlusButton() + .attributes( + .class("btn-primary text-2xl me-2"), + .showModal(id: ComponentLossForm.id()) + ) + .tooltip("Add component loss") } } } diff --git a/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift b/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift index ee11d7f..0b434c1 100644 --- a/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift +++ b/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift @@ -3,8 +3,6 @@ import ElementaryHTMX import ManualDCore import Styleguide -// TODO: Add trunk size table. - struct DuctSizingView: HTML, Sendable { @Environment(ProjectViewValue.$projectID) var projectID @@ -14,32 +12,40 @@ struct DuctSizingView: HTML, Sendable { var body: some HTML { div(.class("space-y-4")) { - PageTitle { "Duct Sizes" } + PageTitleRow { + div(.class("space-y-4")) { + PageTitle("Duct Sizes") - if rooms.count == 0 { - p(.class("text-error italic")) { - "Must complete all the previous sections to display duct sizing calculations." - } - } else { - RoomsTable(rooms: rooms) - div(.class("divider mb-6")) {} - } - - Row { - h2(.class("text-2xl font-bold")) { - "Trunk / Runout Sizes" - } - - PlusButton() - .attributes( - .class("me-6"), - .showModal(id: TrunkSizeForm.id()) + Alert( + """ + Must complete all the previous sections to display duct sizing calculations. + """ ) + .hidden(when: rooms.count > 0) + .attributes(.class("text-error font-bold italic")) + } } - if trunks.count > 0 { - div(.class("divider -mt-2")) {} - TrunkTable(trunks: trunks, rooms: rooms) + if rooms.count != 0 { + RoomsTable(rooms: rooms) + + PageTitleRow { + PageTitle { + "Trunk / Runout Sizes" + } + + PlusButton() + .attributes( + .class("btn-primary"), + .showModal(id: TrunkSizeForm.id()) + ) + .tooltip("Add trunk / runout") + } + + if trunks.count > 0 { + TrunkTable(trunks: trunks, rooms: rooms) + } + } TrunkSizeForm(rooms: rooms, dismiss: true) diff --git a/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift index 531b945..a0ea302 100644 --- a/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift +++ b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift @@ -21,13 +21,14 @@ struct EffectiveLengthsView: HTML, Sendable { var body: some HTML { div(.class("space-y-4")) { - Row { + PageTitleRow { PageTitle { "Equivalent Lengths" } PlusButton() .attributes( - .class("btn-ghost me-4"), + .class("btn-primary"), .showModal(id: EffectiveLengthForm.id(nil)) ) + .tooltip("Add equivalent length") } .attributes(.class("pb-6")) diff --git a/Sources/ViewController/Views/EquipmentInfo/EquipmentInfoView.swift b/Sources/ViewController/Views/EquipmentInfo/EquipmentInfoView.swift index 7ffa955..8d2db0d 100644 --- a/Sources/ViewController/Views/EquipmentInfo/EquipmentInfoView.swift +++ b/Sources/ViewController/Views/EquipmentInfo/EquipmentInfoView.swift @@ -12,16 +12,15 @@ struct EquipmentInfoView: HTML, Sendable { .id("equipmentInfo") ) { - Row { - PageTitle { "Equipment Info" } + PageTitleRow { + PageTitle { "Equipment Details" } - Tooltip("Edit equipment info") { - EditButton() - .attributes( - .class("btn-ghost"), - .showModal(id: EquipmentInfoForm.id) - ) - } + EditButton() + .attributes( + .class("btn-primary"), + .showModal(id: EquipmentInfoForm.id) + ) + .tooltip("Edit equipment details") } if let equipmentInfo { diff --git a/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift b/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift index 93f92f8..5d18d5d 100644 --- a/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift +++ b/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift @@ -42,60 +42,75 @@ struct FrictionRateView: HTML, Sendable { var body: some HTML { div(.class("space-y-6")) { - div(.class("grid grid-cols-2 px-4")) { + PageTitleRow { + div(.class("grid grid-cols-2 px-4 gap-y-4")) { - PageTitle { "Friction Rate" } + PageTitle { "Friction Rate" } - div(.class("space-y-4 justify-end")) { + div(.class("space-y-4 justify-end")) { - if let frictionRateDesignValue { - LabeledContent("Friction Rate Design Value") { - Badge(number: frictionRateDesignValue, digits: 2) - .attributes(.class("\(badgeColor)")) + if let frictionRateDesignValue { + LabeledContent("Friction Rate Design Value") { + Badge(number: frictionRateDesignValue, digits: 2) + .attributes(.class("\(badgeColor)")) + } + .attributes(.class("justify-end")) + } + + if let availableStaticPressure { + LabeledContent("Available Static Pressure") { + Badge(number: availableStaticPressure, digits: 2) + } + .attributes(.class("justify-end")) } - .attributes(.class("justify-end")) } - if let availableStaticPressure { - LabeledContent("Available Static Pressure") { - Badge(number: availableStaticPressure, digits: 2) + div(.class("text-error font-bold italic col-span-2")) { + Alert { + p { + "Must complete previous sections." + } } - .attributes(.class("justify-end")) + .hidden( + when: availableStaticPressure != nil && frictionRateDesignValue != nil + ) + + Alert { + p { + "No component pressures losses" + } + } + .hidden(when: componentLosses.totalComponentPressureLoss > 0) + + Alert { + p(.class("block")) { + "Calculated friction rate is below 0.02. The fan may not deliver the required CFM." + br() + " * Increase the blower speed" + br() + " * Increase the blower size" + br() + " * Decrease the Total Effective Length (TEL)" + } + } + .hidden(when: !showLowErrors) + + Alert { + p(.class("block")) { + "Calculated friction rate is above 0.18. The fan may deliver too many CFM." + br() + " * Decrease the blower speed" + br() + " * Decreae the blower size" + br() + " * Increase the Total Effective Length (TEL)" + } + } + .hidden(when: !showHighErrors) } } } - div(.class("text-error italic")) { - p { - "No component pressures losses" - } - .attributes(.class("hidden"), when: componentLosses.totalComponentPressureLoss > 0) - - p { - "Calculated friction rate is below 0.02. The fan may not deliver the required CFM." - br() - " * Increase the blower speed" - br() - " * Increase the blower size" - br() - " * Decrease the Total Effective Length (TEL)" - } - .attributes(.class("hidden"), when: !showLowErrors) - - p { - "Calculated friction rate is above 0.18. The fan may deliver too many CFM." - br() - " * Decrease the blower speed" - br() - " * Decreae the blower size" - br() - " * Increase the Total Effective Length (TEL)" - } - .attributes(.class("hidden"), when: !showHighErrors) - } - - div(.class("divider")) {} - ComponentPressureLossesView( componentPressureLosses: componentLosses, projectID: projectID ) diff --git a/Sources/ViewController/Views/MainPage.swift b/Sources/ViewController/Views/MainPage.swift index 5abae10..f52c6a0 100644 --- a/Sources/ViewController/Views/MainPage.swift +++ b/Sources/ViewController/Views/MainPage.swift @@ -82,7 +82,7 @@ public struct MainPage: SendableHTMLDocument where Inner: Sendable public var body: some HTML { div(.class("flex flex-col min-h-screen min-w-full")) { - main(.class("overflow-auto grow")) { + main(.class("grow")) { inner } diff --git a/Sources/ViewController/Views/Project/ProjectDetail.swift b/Sources/ViewController/Views/Project/ProjectDetail.swift index cf9ec28..30ce2bd 100644 --- a/Sources/ViewController/Views/Project/ProjectDetail.swift +++ b/Sources/ViewController/Views/Project/ProjectDetail.swift @@ -8,13 +8,15 @@ struct ProjectDetail: HTML, Sendable { var body: some HTML { div { - Row { - h1(.class("text-3xl font-bold")) { "Project" } + PageTitleRow { + PageTitle { "Project" } + EditButton() .attributes( - .class("btn-ghost"), + .class("btn-primary"), .on(.click, "projectForm.showModal()") ) + .tooltip("Edit project", position: .left) } table(.class("table table-zebra text-lg")) { diff --git a/Sources/ViewController/Views/Rooms/RoomsView.swift b/Sources/ViewController/Views/Rooms/RoomsView.swift index a150d2e..1df10b0 100644 --- a/Sources/ViewController/Views/Rooms/RoomsView.swift +++ b/Sources/ViewController/Views/Rooms/RoomsView.swift @@ -13,63 +13,58 @@ struct RoomsView: HTML, Sendable { var body: some HTML { div(.class("flex w-full flex-col")) { - Row { - PageTitle { "Room Loads" } + PageTitleRow { + div(.class("flex grid grid-cols-3 w-full gap-y-4")) { - div(.class("flex justify-end items-end -my-2")) { - Tooltip("Project wide sensible heat ratio", position: .left) { - button( - .class( - """ - justify-end items-end p-4 - hover:bg-neutral hover:text-white hover:rounded-lg - """ - ), - .showModal(id: SHRForm.id) - ) { - LabeledContent { - div(.class("flex justify-end items-end space-x-4")) { - Label { + div(.class("col-span-2")) { + PageTitle { "Room Loads" } + } + + div(.class("flex justify-end grow")) { + Tooltip("Project wide sensible heat ratio", position: .left) { + button( + .class( + """ + btn btn-primary text-lg font-bold py-2 + """ + ), + .showModal(id: SHRForm.id) + ) { + div(.class("flex grow justify-end items-end space-x-4")) { + span { "Sensible Heat Ratio" } - .attributes(.class("me-8"), when: sensibleHeatRatio == nil) - } - } content: { - if let sensibleHeatRatio { - Badge(number: sensibleHeatRatio) - } else { - SVG(.squarePen) + if let sensibleHeatRatio { + Badge(number: sensibleHeatRatio) + } else { + Badge { "set" } + } } } + .attributes(.class("border border-error"), when: sensibleHeatRatio == nil) } - .attributes(.class("border rounded-lg border-error"), when: sensibleHeatRatio == nil) + } + + div(.class("flex items-end space-x-4 font-bold")) { + span(.class("text-lg")) { "Heating Total" } + Badge(number: rooms.heatingTotal, digits: 0) + .attributes(.class("badge-error")) + } + + div(.class("flex justify-center items-end space-x-4 my-auto font-bold")) { + span(.class("text-lg")) { "Cooling Total" } + Badge(number: rooms.coolingTotal, digits: 0) + .attributes(.class("badge-success")) + } + + div(.class("flex grow justify-end items-end space-x-4 me-4 my-auto font-bold")) { + span(.class("text-lg")) { "Cooling Sensible" } + Badge(number: rooms.coolingSensible(shr: sensibleHeatRatio), digits: 0) + .attributes(.class("badge-info")) } } } - div(.class("flex flex-wrap justify-between mt-6")) { - div(.class("flex items-end space-x-4")) { - Label { "Heating Total" } - Badge(number: rooms.heatingTotal, digits: 0) - .attributes(.class("badge-error")) - } - - div(.class("flex items-end space-x-4")) { - Label { "Cooling Total" } - Badge(number: rooms.coolingTotal, digits: 0) - .attributes(.class("badge-success")) - } - - div(.class("flex justify-end items-end space-x-4 me-4")) { - Label { "Cooling Sensible" } - Badge(number: rooms.coolingSensible(shr: sensibleHeatRatio), digits: 0) - .attributes(.class("badge-info")) - } - } - // .attributes(.class("mt-6 me-4")) - - div(.class("divider")) {} - SHRForm(projectID: projectID, sensibleHeatRatio: sensibleHeatRatio) table(.class("table table-zebra text-lg"), .id("roomsTable")) { @@ -101,7 +96,7 @@ struct RoomsView: HTML, Sendable { Tooltip("Add Room") { PlusButton() .attributes( - .class("btn-ghost mx-auto"), + .class("btn-primary mx-auto"), .showModal(id: RoomForm.id()) ) .attributes(.class("tooltip-left"))