feat: Adds styleguide, working on result view container.

This commit is contained in:
2025-02-26 17:08:13 -05:00
parent cce99ce5e9
commit a15e54e0e4
16 changed files with 569 additions and 122 deletions

View File

@@ -1,4 +1,5 @@
import Elementary
import Styleguide
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
let title = "HVAC-Toolbox"
@@ -33,36 +34,36 @@ struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
main(.class("bg-white dark:bg-gray-800")) {
div(.class("min-h-screen")) {
Header()
inner()
PageContent(body: inner)
}
}
}
}
struct Header: HTML {
private struct Header: HTML {
var content: some HTML {
header(.class("bg-blue-500 mb-8 flex flex-row gap-2 border-b border-yellow-300")) {
header(.class("\(bg: .blue) mb-8 flex flex-row gap-2 border \(border: .yellow)")) {
a(
.href(route: .index),
.class("flex flex-row gap-2 bg-yellow-300 pe-2 rounded-e-lg text-blue-500 [&:hover]:text-blue-600")
.class("flex flex-row gap-2 \(bg: .yellow) pe-2 rounded-e-lg \(text: .blue) hover:\(text: .darkBlue)")
) {
img(.src("/images/toolbox.svg"), .width(40), .height(40), .class("py-1"))
div(.class("flex flex-row mt-2")) {
h2(.class("text-2xl font-extrabold")) { "HVAC-Toolbox" }
SVG.wind
h2(.class("text-2xl font-extrabold pe-3")) { "HVAC-Toolbox" }
SVG(.wind, color: .blue)
}
}
nav(.class("flex flex-row gap-2 p-2 mt-2")) {
// TODO: Add class active, to button that is the active route.
ul(.class("flex flex-wrap gap-x-2 lg:gap-x-5 text-yellow-300 font-bold")) {
ul(.class("flex flex-wrap gap-x-2 lg:gap-x-5 \(text: .yellow) font-bold")) {
li {
a(.href(route: .moldRisk(.index)), .class("[&:hover]:border-b border-yellow-300")) {
a(.href(route: .moldRisk(.index)), .class("hover:border-b \(border: .yellow)")) {
"Mold-Risk"
}
}
li {
a(.href("#"), .class("[&:hover]:border-b border-yellow-300")) {
a(.href("#"), .class("[&:hover]:border-b \(border: .yellow)")) {
"Dehumidifier-Sizing"
}
}
@@ -72,4 +73,16 @@ struct Header: HTML {
}
}
private struct PageContent<Body: HTML>: HTML where Body: Sendable {
let body: () -> Body
var content: some HTML {
div(.class("mx-5 lg:mx-20")) {
div(.class("rounded-xl shadow-lg \(bg: .slate) dark:\(bg: .darkSlate) p-8")) {
body()
}
}
}
}
protocol SendableHTMLDocument: HTMLDocument, Sendable {}

View File

@@ -1,56 +1,91 @@
import Elementary
import PsychrometricClient
import Routes
import Styleguide
struct MoldRiskForm: HTML {
var content: some HTML {
div(.class("grid grid-cols-1 lg:grid-cols-12")) {
div(.class("col-span-1")) {}
div(.class("col-span-10 rounded-xl shadow-lg bg-slate-300 dark:bg-slate-700 p-8")) {
div(.class("flex items-center gap-3 mb-6")) {
SVG.thermometer
h2(.class("text-2xl font-extrabold dark:text-white")) { "Mold Risk Calculator" }
FormHeader(label: "Mold Risk Calculator", svg: .thermometer)
form {
div(.class("space-y-6")) {
LabeledContent(label: "Indoor Temperature (°F)") {
Input(id: "temperature", placeholder: "Dry bulb temperature")
.attributes(.type(.number), .step("0.1"), .min("0.1"), .autofocus)
}
form {
div(.class("space-y-6")) {
div {
label(.for("temperature"), .class("block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2")) {
"Indoor Temperature"
}
input(
.type(.number), .id("temperature"), .placeholder("Dry bulb temperature"), .required,
.step("0.1"), .min("0.1"),
.class("""
w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-yellow-800
focus:border-yellow-800 text-gray-700 dark:text-white
""")
)
}
div {
label(.for("humidity"), .class("block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2")) {
"Indoor Humidity (%)"
}
input(
.type(.number), .id("humidity"), .name("humidity"), .placeholder("Relative humidity"), .required,
.step("0.1"), .min("0.1"),
.class("""
w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-yellow-800
focus:border-yellow-800 text-gray-700 dark:text-white
""")
)
}
div {
button(.type(.submit), .class("""
w-full bg-\(Colors.blue) text-\(Colors.yellow) font-bold py-3 rounded-md hover:bg-blue-600
transition-colors
""")) {
"Calculate Mold Risk"
}
}
}
LabeledContent(label: "Indor Humdity (%)") {
Input(id: "humidity", placeholder: "Relative humidity")
.attributes(.type(.number), .step("0.1"), .min("0.1"))
}
div {
SubmitButton(label: "Calculate Mold Risk")
}
div(.id("result")) {
MoldRiskResponse(response: .mock)
}
div(.class("col-span-1")) {}
}
}
}
}
struct MoldRiskResponse: HTML {
let response: MoldRisk.Response
var content: some HTML {
ResultContainer {
// TODO: Color needs to be derived from risk level.
div(.class("p-2 rounded-lg shadow-lg bg-lime-100 border-2 border border-lime-600")) {
LabeledContent {
p(.class("text-lg font-semibold \(text: .green) dark:text-lime-600 mt-2")) {
"Risk Level: \(response.riskLevel.rawValue.capitalized)"
}
} label: {
SVG(.exclamation, color: "\(text: .green) dark:text-lime-600")
}
.attributes(.class("flex items-center gap-2"))
}
PsychrometricPropertiesGrid(properties: response.psychrometricProperties)
.attributes(.class("mx-6"))
div(.class("mt-8 p-4 bg-gray-700 rounded-md shadow-md border border-blue-500")) {
p(.class("text-sm \(text: .blue)")) {
span(.class("font-extrabold pe-2")) { "Note:" }
"""
These calculations are based on typical indoor conditions and common mold species. Actual mold growth can
vary based on surface materials, air movement, and other environmental factors. Always address moisture
issues promptly and consult professionals for severe cases.
"""
}
}
}
}
}
struct PsychrometricPropertiesGrid: HTML {
let properties: PsychrometricProperties
var content: some HTML<HTMLTag.div> {
div(.class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-6 md:mx-20")) {
displayProperty("Dew Point", "\(properties.dewPoint.value) \(properties.dewPoint.units.symbol)")
displayProperty("Wet Bulb", "\(properties.wetBulb.value) \(properties.wetBulb.units.symbol)")
displayProperty("Enthalpy", "\(properties.enthalpy.value) \(properties.wetBulb.units.symbol)")
displayProperty("Density", "\(properties.density.value) \(properties.density.units.rawValue)")
displayProperty("Vapor Pressure", "\(properties.vaporPressure.value) \(properties.vaporPressure.units.symbol)")
displayProperty("Specific Volume", "\(properties.specificVolume.rawValue)")
displayProperty("Absolute Humidity", "\(properties.absoluteHumidity.value) \(properties.absoluteHumidity.units.symbol)")
displayProperty("Humidity Ratio", "\(properties.humidityRatio.value)")
displayProperty("Degree of Saturation", "\(properties.degreeOfSaturation.value)")
}
}
func displayProperty(_ label: String, _ value: String) -> some HTML {
p(.class("\(text: .darkGray) dark:\(text: .white)")) {
span(.class("font-semibold")) { "\(label): " }
span(.class("font-light")) { value }
}
}
}

View File

@@ -1,46 +0,0 @@
import Elementary
enum SVG {
static let wind = HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="w-8 h-8">
<path d="M17.7 7.7a2.5 2.5 0 1 1 1.8 4.3H2"></path>
<path d="M9.6 4.6A2 2 0 1 1 11 8H2"></path>
<path d="M12.6 19.4A2 2 0 1 0 14 16H2"></path>
</svg>
""")
static let calculator = HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="w-5 h-5">
<rect width="16" height="20" x="4" y="2" rx="2"></rect>
<line x1="8" x2="16" y1="6" y2="6"></line>
<line x1="16" x2="16" y1="14" y2="18"></line>
<path d="M16 10h.01"></path>
<path d="M12 10h.01"></path>
<path d="M8 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M8 14h.01"></path>
<path d="M12 18h.01"></path><path d="M8 18h.01"></path>
</svg>
""")
static let thermometer = HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="w-8 h-8 text-blue-500">
<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"></path>
</svg>
""")
static let menu = HTMLRaw("""
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
viewBox="0 0 24 24" x="0" y="0" id="menu-icon">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
""")
}

View File

@@ -1,7 +0,0 @@
// NOTE: These don't always work as expected with tailwind they generally
// need to be in the class itself, but here for reference of the primary
// colors.
enum Colors {
static let yellow = "yellow-300"
static let blue = "blue-500"
}