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,5 +1,5 @@
{
"originHash" : "7efd57cbec8a157adddec3ffcc6ff2b58e602f8762df879e933220d7070945f7",
"originHash" : "af48ddc16f0ca460cff188345cf870056ca53edee23c016080fc1ad55f366bf9",
"pins" : [
{
"identity" : "async-http-client",
@@ -276,8 +276,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-psychrometrics/swift-psychrometrics.git",
"state" : {
"revision" : "df8b764b06b52386948b2ddcd99cdf000db666ec",
"version" : "0.2.4"
"revision" : "04f3324b2e3498852461b43102b8835136f2f0e7",
"version" : "0.3.0"
}
},
{

View File

@@ -10,6 +10,7 @@ let package = Package(
.executable(name: "App", targets: ["App"]),
.library(name: "ApiController", targets: ["ApiController"]),
.library(name: "Routes", targets: ["Routes"]),
.library(name: "Styleguide", targets: ["Styleguide"]),
.library(name: "ViewController", targets: ["ViewController"])
],
dependencies: [
@@ -25,7 +26,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.7"),
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
.package(url: "https://github.com/swift-psychrometrics/swift-psychrometrics.git", from: "0.2.4")
.package(url: "https://github.com/swift-psychrometrics/swift-psychrometrics.git", from: "0.3.0")
],
targets: [
.executableTarget(
@@ -68,10 +69,18 @@ let package = Package(
],
swiftSettings: swiftSettings
),
.target(
name: "Styleguide",
dependencies: [
.product(name: "Elementary", package: "elementary")
],
swiftSettings: swiftSettings
),
.target(
name: "ViewController",
dependencies: [
"Routes",
"Styleguide",
.product(name: "DependenciesMacros", package: "swift-dependencies"),
.product(name: "Elementary", package: "elementary")
],

View File

@@ -1,15 +1,8 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="-102.4 -102.4 1228.80 1228.80" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#1E2938" stroke="#1E2938" stroke-width="15.36">
<g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(97.27999999999997,97.27999999999997), scale(0.81)">
<rect x="-102.4" y="-102.4" width="1228.80" height="1228.80" rx="0" fill="#FFDE5A" strokewidth="0"/>
</g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="38.912">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

View File

@@ -40,3 +40,32 @@ public enum MoldRisk {
case severe
}
}
#if DEBUG
import Dependencies
public extension MoldRisk.Response {
static var mock: Self {
return .init(
psychrometricProperties: .init(
absoluteHumidity: .zero,
atmosphericPressure: .zero,
degreeOfSaturation: .zero,
density: .zero, dewPoint: 59.4,
dryBulb: 75,
enthalpy: .zero,
grainsOfMoisture: .zero,
humidityRatio: .zero,
relativeHumidity: 50%,
specificVolume: .zero,
vaporPressure: .zero,
wetBulb: .zero,
units: .imperial
),
riskLevel: .low,
recommendations: []
)
}
}
#endif

View File

@@ -0,0 +1,19 @@
import Elementary
public struct SubmitButton: HTML, Sendable {
let label: String
public init(label: String) {
self.label = label
}
public var content: some HTML<HTMLTag.button> {
button(
.type(.submit),
.class("""
w-full \(bg: .blue) \(text: .yellow) font-bold py-3 rounded-md
hover:\(bg: .darkBlue) transition-colors
""")
) { label }
}
}

View File

@@ -0,0 +1,85 @@
/// Common tailwind colors used.
///
/// Use these with string interpolation in the class.
///
/// **Example:**
///
/// ```swift
/// div(.class("\(text: .yellow) \(border: .gray) \(bg: .blue)")) {
/// ...
/// }
/// ```
public enum Color {
public enum BackgroundColor: Sendable {
case blue
case darkBlue
case darkGray
case darkSlate
case slate
case yellow
var color: String {
switch self {
case .blue: return "bg-blue-500"
case .darkBlue: return "bg-blue-600"
case .darkGray: return "bg-gray-700"
case .darkSlate: return "bg-slate-700"
case .slate: return "bg-slate-300"
case .yellow: return "bg-yellow-300"
}
}
}
public enum BorderColor: Sendable {
case darkYellow
case gray
case yellow
var color: String {
switch self {
case .darkYellow: return "border-yellow-800"
case .gray: return "border-gray-300"
case .yellow: return "border-yellow-300"
}
}
}
public enum TextColor: Sendable {
case blue
case darkBlue
case darkGray
case gray
case green
case yellow
case white
var color: String {
switch self {
case .blue: return "text-blue-500"
case .darkBlue: return "text-blue-600"
case .darkGray: return "text-gray-700"
case .gray: return "text-gray-300"
case .green: return "text-green-600"
case .yellow: return "text-yellow-300"
case .white: return "text-white"
}
}
}
}
public extension String.StringInterpolation {
mutating func appendInterpolation(bg background: Color.BackgroundColor) {
appendInterpolation(background.color)
}
mutating func appendInterpolation(border: Color.BorderColor) {
appendInterpolation(border.color)
}
mutating func appendInterpolation(text: Color.TextColor) {
appendInterpolation(text.color)
}
}

View File

@@ -0,0 +1,84 @@
import Elementary
/// A styled header for a form element, which consists of an
/// svg image and label / name for the form.
///
public struct FormHeader: HTML, Sendable {
let label: String
let svg: SVGType
public init(
label: String,
svg: SVGType
) {
self.label = label
self.svg = svg
}
public var content: some HTML {
LabeledContent {
h2(.class("text-2xl font-extrabold dark:\(text: .white)")) { label }
} label: {
SVG(svg, color: .blue)
}
.attributes(.class("flex items-center gap-3 mb-6"))
}
}
/// A styled form input, does not contain the input type which is generally
/// added at the call site.
///
/// **Example:**
/// ```swift
/// Input(id: "email", placeholder: "Email")
/// .attributes(.type(.email))
/// ```
///
public struct Input: HTML, Sendable {
let id: String
let name: String?
let placeholder: String
public init(id: String, name: String? = nil, placeholder: String) {
self.id = id
self.name = name
self.placeholder = placeholder
}
public var content: some HTML<HTMLTag.input> {
input(
.id(id), .placeholder(placeholder), .name(name ?? id),
.class("""
w-full px-4 py-2 border \(border: .gray) rounded-md focus:ring-2
focus:ring-yellow-800 focus:border-yellow-800 \(text: .darkGray) dark:\(text: .white)
""")
)
}
}
/// A style form input label.
public struct InputLabel<InputLabel: HTML>: HTML {
let forInputId: String
let inputLabel: InputLabel
public init(
for forInputId: String,
@HTMLBuilder label: () -> InputLabel
) {
self.forInputId = forInputId
self.inputLabel = label()
}
public var content: some HTML<HTMLTag.label> {
label(
.for(forInputId),
.class("block text-sm font-medium \(text: .darkGray) dark:\(text: .gray) mb-2")
) {
self.inputLabel
}
}
}
extension InputLabel: Sendable where InputLabel: Sendable {}

View File

@@ -0,0 +1,54 @@
import Elementary
public struct LabeledContent<Label: HTML, Body: HTML>: HTML {
let body: Body
let label: Label
public init(
@HTMLBuilder body: () -> Body,
@HTMLBuilder label: () -> Label
) {
self.body = body()
self.label = label()
}
public var content: some HTML<HTMLTag.div> {
div {
label
body
}
}
}
extension LabeledContent: Sendable where Label: Sendable, Body: Sendable {}
// MARK: - Forms
public extension LabeledContent where Label == InputLabel<HTMLText>, Body == Input {
init(
label: String,
input: () -> Body
) {
self.init {
input()
} label: {
InputLabel(for: input().id) { HTMLText(label) }
}
}
}
public extension LabeledContent where Label == InputLabel<HTMLText>, Body == _AttributedElement<Input> {
init(
label: String,
input: () -> Body
) {
self.init {
input()
} label: {
InputLabel(for: input().content.id) { HTMLText(label) }
}
}
}

View File

@@ -0,0 +1,20 @@
import Elementary
public struct ResultContainer<Body: HTML>: HTML {
let body: Body
public init(
@HTMLBuilder body: () -> Body
) {
self.body = body()
}
public var content: some HTML {
div(.class("mt-6 p-6 bg-blue-50 dark:bg-slate-400 rounded-lg")) {
h3(.class("text-xl font-semibold \(text: .darkGray) mb-4")) { "Results" }
body
}
}
}
extension ResultContainer: Sendable where Body: Sendable {}

View File

@@ -0,0 +1,158 @@
import Elementary
public struct SVG: HTML, Sendable {
let color: String
let size: SVGSize
let svg: SVGType
public init(
_ svg: SVGType,
color: String,
size: SVGSize = .init()
) {
self.svg = svg
self.size = size
self.color = color
}
public init(
_ svg: SVGType,
color: Color.TextColor,
size: SVGSize = .init()
) {
self.svg = svg
self.size = size
self.color = color.color
}
public var content: some HTML<HTMLTag.div> {
div(.class("block \(color)")) {
svg.html(size)
}
}
}
public struct SVGSize: Sendable {
let width: Int
let height: Int
public init(width: Int = 24, height: Int? = nil) {
self.width = width
self.height = height ?? width
}
}
public enum SVGType: Sendable {
case calculator
case exclamation
case menu
case thermometer
case toolbox
case wind
public func html(_ size: SVGSize) -> some HTML {
switch self {
case .calculator: return calculatorSvg(size: size)
case .exclamation: return exclamationSvg(size: size)
case .menu: return menuSvg(size: size)
case .thermometer: return thermometerSvg(size: size)
case .toolbox: return toolboxSvg(size: size)
case .wind: return windSvg(size: size)
}
}
}
// MARK: - SVGs
// swiftlint:disable line_length
// TODO: Requires attribution:
// Vectors and icons by <a href="https://dribbble.com/Laridae?ref=svgrepo.com" target="_blank">Laridae</a> in CC Attribution License via <a href="https://www.svgrepo.com/" target="_blank">SVG Repo</a>
// FIX: This doesn't work, but does as a file
private func toolboxSvg(size: SVGSize) -> HTMLRaw {
HTMLRaw("""
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="\(size.width)" height="\(size.height)" stroke="currentColor" viewBox="0 0 \(size.width) \(size.height)" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000">
<g id="SVGRepo_bgCarrier" stroke="currentColor" stroke-width="2"/>
<g id="SVGRepo_tracerCarrier" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path d="M619.52 194.56h35.84V128c0-5.632-4.608-10.24-10.24-10.24H378.88c-5.632 0-10.24 4.608-10.24 10.24v66.56h35.84v-30.72c0-5.632 4.608-10.24 10.24-10.24h194.56c5.632 0 10.24 4.608 10.24 10.24v30.72z" fill="#2C7FFF"/>
<path d="M184.32 209.92v20.48h655.36v-20.48H184.32z m-5.12-15.36h665.6c5.632 0 10.24 4.608 10.24 10.24v30.72c0 5.632-4.608 10.24-10.24 10.24H179.2c-5.632 0-10.24-4.608-10.24-10.24v-30.72c0-5.632 4.608-10.24 10.24-10.24zM240.64 890.88h51.2v-10.24H240.64v10.24z m-5.12-25.6h61.44c5.632 0 10.24 4.608 10.24 10.24v20.48c0 5.632-4.608 10.24-10.24 10.24H235.52c-5.632 0-10.24-4.608-10.24-10.24v-20.48c0-5.632 4.608-10.24 10.24-10.24zM737.28 890.88h51.2v-10.24h-51.2v10.24z m-5.12-25.6h61.44c5.632 0 10.24 4.608 10.24 10.24v20.48c0 5.632-4.608 10.24-10.24 10.24h-61.44c-5.632 0-10.24-4.608-10.24-10.24v-20.48c0-5.632 4.608-10.24 10.24-10.24z" fill=""/>
<path d="M199.68 343.04h122.88c5.632 0 10.24 4.608 10.24 10.24v81.92c0 5.632-4.608 10.24-10.24 10.24H199.68c-5.632 0-10.24-4.608-10.24-10.24V353.28c0-5.632 4.608-10.24 10.24-10.24zM701.44 343.04h122.88c5.632 0 10.24 4.608 10.24 10.24v81.92c0 5.632-4.608 10.24-10.24 10.24h-122.88c-5.632 0-10.24-4.608-10.24-10.24V353.28c0-5.632 4.608-10.24 10.24-10.24z" fill="#2C7FFF"/>
<path d="M873.472 276.48H148.48c-11.264 1.024-19.456 10.752-18.432 22.016l48.128 522.24c1.024 10.752 9.728 18.432 20.48 18.432h626.688c10.752 0 19.456-8.192 20.48-18.432l48.64-522.24v-2.048c-0.512-10.752-9.728-19.968-20.992-19.968zM150.528 291.84h722.944c3.072 0 5.12 2.048 5.12 5.632L870.4 384h-35.84v-30.72c0-5.632-4.608-10.24-10.24-10.24h-122.88c-5.632 0-10.24 4.608-10.24 10.24v30.72H332.8v-30.72c0-5.632-4.608-10.24-10.24-10.24H199.68c-5.632 0-10.24 4.608-10.24 10.24v30.72h-36.352l-8.192-86.528c0-3.072 2.048-5.12 5.632-5.632zM819.2 358.4v71.68h-112.64V358.4h112.64z m-501.76 0v71.68H204.8V358.4h112.64z m513.024 461.312c0 2.56-2.56 4.608-5.12 4.608H198.144c-2.56 0-4.608-2.048-5.12-4.608L154.624 399.36H189.44v35.84c0 5.632 4.608 10.24 10.24 10.24h122.88c5.632 0 10.24-4.608 10.24-10.24v-35.84h358.4v35.84c0 5.632 4.608 10.24 10.24 10.24h122.88c5.632 0 10.24-4.608 10.24-10.24v-35.84h34.816l-38.912 420.352z" fill=""/>
</g>
</svg>
""")
}
private func exclamationSvg(size: SVGSize) -> HTMLRaw {
HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-6 h-6">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
<path d="M12 9v4"></path>
<path d="M12 17h.01"></path>
</svg>
""")
}
private func windSvg(size: SVGSize) -> HTMLRaw {
return HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" 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>
""")
}
private func calculatorSvg(size: SVGSize) -> HTMLRaw {
return HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" 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>
""")
}
private func thermometerSvg(size: SVGSize) -> HTMLRaw {
HTMLRaw("""
<svg xmlns="http://www.w3.org/2000/svg" width="\(size.width)" height="\(size.height)" viewBox="0 0 \(size.width) \(size.height)" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="w-8 h-8">
<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z"></path>
</svg>
""")
}
private func menuSvg(size: SVGSize) -> HTMLRaw {
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>
""")
}
// swiftlint:enable line_length

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"
}

View File

@@ -6,6 +6,7 @@ module.exports = {
content: [
"./Sources/ViewControllerLive/Views/*.swift",
"./Sources/ViewControllerLive/*.swift",
"./Public/images/*.svg"
"./Public/images/*.svg",
"./Sources/Styleguide/*.swift"
],
};