From 6c6045b4a6f3f8983c2e3f193f21a171b919a64f Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Fri, 2 Jan 2026 22:53:22 -0500 Subject: [PATCH] WIP: Updates to login / signup forms, rearranges some view routes. --- Sources/ApiController/Live.swift | 4 +- Sources/DatabaseClient/Projects.swift | 29 +- Sources/ManualDCore/Project.swift | 2 +- Sources/ManualDCore/Routes/ViewRoute.swift | 15 +- Sources/Styleguide/ElementaryExtensions.swift | 9 + Sources/ViewController/Live.swift | 85 +- .../ViewController/Views/User/LoginForm.swift | 166 +- .../Views/User/SignupForm.swift | 0 output.css | 1523 ++++++++++++++++- swift-dev | 2 +- 10 files changed, 1641 insertions(+), 194 deletions(-) create mode 100644 Sources/Styleguide/ElementaryExtensions.swift delete mode 100644 Sources/ViewController/Views/User/SignupForm.swift diff --git a/Sources/ApiController/Live.swift b/Sources/ApiController/Live.swift index 5add1ff..67b2d33 100644 --- a/Sources/ApiController/Live.swift +++ b/Sources/ApiController/Live.swift @@ -25,7 +25,9 @@ extension SiteRoute.Api.ProjectRoute { switch self { case .create(let request): - return try await database.projects.create(request) + // return try await database.projects.create(request) + // FIX: + fatalError() case .delete(let id): try await database.projects.delete(id) return nil diff --git a/Sources/DatabaseClient/Projects.swift b/Sources/DatabaseClient/Projects.swift index 796b965..a1b4c07 100644 --- a/Sources/DatabaseClient/Projects.swift +++ b/Sources/DatabaseClient/Projects.swift @@ -7,21 +7,20 @@ import ManualDCore extension DatabaseClient { @DependencyClient public struct Projects: Sendable { - public var create: @Sendable (Project.Create) async throws -> Project + public var create: @Sendable (User.ID, Project.Create) async throws -> Project public var delete: @Sendable (Project.ID) async throws -> Void public var get: @Sendable (Project.ID) async throws -> Project? + public var fetch: @Sendable (User.ID) async throws -> [Project] } } extension DatabaseClient.Projects: TestDependencyKey { public static let testValue = Self() -} -extension DatabaseClient.Projects { public static func live(database: any Database) -> Self { .init( - create: { request in - let model = try request.toModel() + create: { userID, request in + let model = try request.toModel(userID: userID) try await model.save(on: database) return try model.toDTO() }, @@ -33,6 +32,13 @@ extension DatabaseClient.Projects { }, get: { id in try await ProjectModel.find(id, on: database).map { try $0.toDTO() } + }, + fetch: { userID in + try await ProjectModel.query(on: database) + .with(\.$user) + .filter(\.$user.$id == userID) + .all() + .map { try $0.toDTO() } } ) } @@ -40,14 +46,15 @@ extension DatabaseClient.Projects { extension Project.Create { - func toModel() throws -> ProjectModel { + func toModel(userID: User.ID) throws -> ProjectModel { try validate() return .init( name: name, streetAddress: streetAddress, city: city, state: state, - zipCode: zipCode + zipCode: zipCode, + userID: userID ) } @@ -84,7 +91,8 @@ extension Project { .field("zipCode", .string, .required) .field("createdAt", .datetime) .field("updatedAt", .datetime) - .unique(on: "name") + .field("userID", .uuid, .required, .references(UserModel.schema, "id")) + .unique(on: "userID", "name") .create() } @@ -126,6 +134,9 @@ final class ProjectModel: Model, @unchecked Sendable { @Children(for: \.$project) var componentLosses: [ComponentLossModel] + @Parent(key: "userID") + var user: UserModel + init() {} init( @@ -135,6 +146,7 @@ final class ProjectModel: Model, @unchecked Sendable { city: String, state: String, zipCode: String, + userID: User.ID, createdAt: Date? = nil, updatedAt: Date? = nil ) { @@ -145,6 +157,7 @@ final class ProjectModel: Model, @unchecked Sendable { self.city = city self.state = state self.zipCode = zipCode + $user.id = userID self.createdAt = createdAt self.updatedAt = updatedAt } diff --git a/Sources/ManualDCore/Project.swift b/Sources/ManualDCore/Project.swift index c70e723..225ce3d 100644 --- a/Sources/ManualDCore/Project.swift +++ b/Sources/ManualDCore/Project.swift @@ -48,7 +48,7 @@ extension Project { streetAddress: String, city: String, state: String, - zipCode: String + zipCode: String, ) { self.name = name self.streetAddress = streetAddress diff --git a/Sources/ManualDCore/Routes/ViewRoute.swift b/Sources/ManualDCore/Routes/ViewRoute.swift index 050ff4e..b6e18e8 100644 --- a/Sources/ManualDCore/Routes/ViewRoute.swift +++ b/Sources/ManualDCore/Routes/ViewRoute.swift @@ -7,6 +7,7 @@ extension SiteRoute { /// /// The routes return html. public enum View: Equatable, Sendable { + case login(LoginRoute) case project(ProjectRoute) case room(RoomRoute) case frictionRate(FrictionRateRoute) @@ -14,6 +15,9 @@ extension SiteRoute { case user(UserRoute) public static let router = OneOf { + Route(.case(Self.login)) { + SiteRoute.View.LoginRoute.router + } Route(.case(Self.project)) { SiteRoute.View.ProjectRoute.router } @@ -180,13 +184,9 @@ extension SiteRoute.View.EffectiveLengthRoute { extension SiteRoute.View { public enum UserRoute: Equatable, Sendable { - case login(Login) case signup(Signup) public static let router = OneOf { - Route(.case(Self.login)) { - SiteRoute.View.UserRoute.Login.router - } Route(.case(Self.signup)) { SiteRoute.View.UserRoute.Signup.router } @@ -194,9 +194,9 @@ extension SiteRoute.View { } } -extension SiteRoute.View.UserRoute { +extension SiteRoute.View { - public enum Login: Equatable, Sendable { + public enum LoginRoute: Equatable, Sendable { case index case submit(User.Login) @@ -220,6 +220,9 @@ extension SiteRoute.View.UserRoute { } } } +} + +extension SiteRoute.View.UserRoute { public enum Signup: Equatable, Sendable { case index diff --git a/Sources/Styleguide/ElementaryExtensions.swift b/Sources/Styleguide/ElementaryExtensions.swift new file mode 100644 index 0000000..b5672c4 --- /dev/null +++ b/Sources/Styleguide/ElementaryExtensions.swift @@ -0,0 +1,9 @@ +import Elementary +import ManualDCore + +extension HTMLAttribute where Tag: HTMLTrait.Attributes.href { + + public static func href(route: SiteRoute.View) -> Self { + href(SiteRoute.View.router.path(for: route)) + } +} diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift index 9711c3b..c1cc1e4 100644 --- a/Sources/ViewController/Live.swift +++ b/Sources/ViewController/Live.swift @@ -5,6 +5,8 @@ extension ViewController.Request { func render() async throws -> AnySendableHTML { switch route { + case .login(let route): + return try await route.renderView(isHtmxRequest: isHtmxRequest) case .project(let route): return try await route.renderView(isHtmxRequest: isHtmxRequest) case .room(let route): @@ -17,23 +19,24 @@ extension ViewController.Request { return try await route.renderView(isHtmxRequest: isHtmxRequest) default: // FIX: FIX - return mainPage + return _render(isHtmxRequest: false) { + div { "Fix me!" } + } } } } extension SiteRoute.View.ProjectRoute { func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { - switch self { - case .index: - return MainPage(active: .projects) { + _render(isHtmxRequest: isHtmxRequest) { + switch self { + case .index: ProjectView(project: .mock) + case .form(let dismiss): + ProjectForm(dismiss: dismiss) + case .create: + div { "Fix me!" } } - case .form(let dismiss): - return ProjectForm(dismiss: dismiss) - - case .create: - return mainPage } } } @@ -44,7 +47,7 @@ extension SiteRoute.View.RoomRoute { case .form(let dismiss): return RoomForm(dismiss: dismiss) case .index: - return MainPage(active: .rooms) { + return _render(isHtmxRequest: isHtmxRequest, active: .rooms) { RoomsView(rooms: Room.mocks) } } @@ -55,7 +58,7 @@ extension SiteRoute.View.FrictionRateRoute { func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { switch self { case .index: - return MainPage(active: .frictionRate) { + return _render(isHtmxRequest: isHtmxRequest, active: .frictionRate) { FrictionRateView() } case .form(let type, let dismiss): @@ -86,7 +89,7 @@ extension SiteRoute.View.EffectiveLengthRoute { func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { switch self { case .index: - return MainPage(active: .effectiveLength) { + return _render(isHtmxRequest: isHtmxRequest, active: .effectiveLength) { EffectiveLengthsView(effectiveLengths: EffectiveLength.mocks) } case .form(let dismiss): @@ -107,12 +110,12 @@ extension SiteRoute.View.UserRoute { func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { switch self { - case .login(.index): - return MainPage(active: .projects, showSidebar: false) { - LoginForm() - } + // case .login(.index): + // return _render(isHtmxRequest: isHtmxRequest, showSidebar: false) { + // LoginForm() + // } case .signup(.index): - return MainPage(active: .projects, showSidebar: false) { + return _render(isHtmxRequest: isHtmxRequest, showSidebar: false) { LoginForm(style: .signup) } default: @@ -121,31 +124,33 @@ extension SiteRoute.View.UserRoute { } } -private let mainPage: AnySendableHTML = { - MainPage(active: .projects) { - div { - h1 { "It works!" } +extension SiteRoute.View.LoginRoute { + func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { + _render(isHtmxRequest: isHtmxRequest, showSidebar: false) { + switch self { + case .index: + LoginForm() + case .submit: + // FIX: + div { "Fix me!" } + } } } -}() +} -@Sendable -private func render( - _ mainPage: (C) async throws -> AnySendableHTML, - _ isHtmxRequest: Bool, - @HTMLBuilder html: () -> C -) async rethrows -> AnySendableHTML where C: Sendable { - guard isHtmxRequest else { - return try await mainPage(html()) +private func _render( + isHtmxRequest: Bool, + active activeTab: Sidebar.ActiveTab = .projects, + showSidebar: Bool = true, + @HTMLBuilder inner: () -> C +) -> AnySendableHTML where C: Sendable { + if isHtmxRequest { + return inner() + } + return MainPage( + active: activeTab, + showSidebar: showSidebar + ) { + inner() } - return html() -} - -@Sendable -private func render( - _ mainPage: (C) async throws -> AnySendableHTML, - _ isHtmxRequest: Bool, - _ html: @autoclosure @escaping () -> C -) async rethrows -> AnySendableHTML where C: Sendable { - try await render(mainPage, isHtmxRequest) { html() } } diff --git a/Sources/ViewController/Views/User/LoginForm.swift b/Sources/ViewController/Views/User/LoginForm.swift index 1716a95..2fb6cbd 100644 --- a/Sources/ViewController/Views/User/LoginForm.swift +++ b/Sources/ViewController/Views/User/LoginForm.swift @@ -11,135 +11,77 @@ struct LoginForm: HTML, Sendable { } var body: some HTML { - form { - fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) { - legend(.class("fieldset-legend")) { style.title } + div( + .id("loginForm"), + .class("flex items-center justify-center") + ) { + form { + fieldset(.class("fieldset bg-base-200 border-base-300 rounded-box w-xl border p-4")) { + legend(.class("fieldset-legend")) { style.title } + + if style == .signup { + label(.class("input validator w-full")) { + SVG(.user) + input( + .type(.text), .required, .placeholder("Username"), + .minlength("3"), .pattern(.username) + ) + } + div(.class("validator-hint hidden")) { + "Enter valid username" + br() + "Must be at least 3 characters" + } + } - if style == .signup { label(.class("input validator w-full")) { - SVG(.user) + SVG(.email) input( - .type(.text), .required, .placeholder("Username"), - .minlength("3"), .pattern(.username) + .type(.email), .placeholder("Email"), .required ) } - div(.class("validator-hint hidden")) { - "Enter valid username" - br() - "Must be at least 3 characters" - } - } + div(.class("validator-hint hidden")) { "Enter valid email address." } - label(.class("input validator w-full")) { - SVG(.email) - input( - .type(.email), .placeholder("Email"), .required - ) - } - div(.class("validator-hint hidden")) { "Enter valid email address." } - - label(.class("input validator w-full")) { - SVG(.key) - input( - .type(.password), .placeholder("Password"), .required, - .pattern(.password), .minlength("8") - ) - } - - if style == .signup { label(.class("input validator w-full")) { SVG(.key) input( - .type(.password), .placeholder("Confirm Password"), .required, + .type(.password), .placeholder("Password"), .required, .pattern(.password), .minlength("8") ) } - } - div(.class("validator-hint hidden")) { - p { - "Must be more than 8 characters, including" - br() - "At least one number" - br() - "At least one lowercase letter" - br() - "At least one uppercase letter" + if style == .signup { + label(.class("input validator w-full")) { + SVG(.key) + input( + .type(.password), .placeholder("Confirm Password"), .required, + .pattern(.password), .minlength("8") + ) + } + } + + div(.class("validator-hint hidden")) { + p { + "Must be more than 8 characters, including" + br() + "At least one number" + br() + "At least one lowercase letter" + br() + "At least one uppercase letter" + } + } + + button(.class("btn btn-secondary mt-4")) { style.title } + a( + .class("btn btn-link mt-4"), + .href(route: style == .signup ? .login(.index) : .user(.signup(.index))) + ) { + style == .login ? "Sign Up" : "Login" } } - - button(.class("btn btn-neutral mt-4")) { style.title } } } - // div(.class("flex items-center justify-center")) { - // div(.class("w-full mx-auto")) { - // h1(.class("text-2xl font-bold")) { style.title } - // form( - // .class("w-full h-screen") - // ) { - // fieldset(.class("fieldset w-full")) { - // legend(.class("fieldset-legend")) { "Email" } - // label(.class("input validator")) { - // SVG(.email) - // input( - // .type(.email), .placeholder("mail@site.com"), .required, - // .autofocus - // ) - // } - // div(.class("validator-hint hidden")) { "Enter valid email address." } - // } - // - // if style == .signup { - // fieldset(.class("fieldset")) { - // legend(.class("fieldset-legend")) { "Name" } - // label(.class("input validator")) { - // input( - // .type(.text), .placeholder("Username"), .required, - // .init(name: "pattern", value: "[A-Za-z][A-Za-z0-9\\-]*"), - // .init(name: "minlength", value: "3") - // ) - // } - // div(.class("validator-hint hidden")) { "Enter valid email address." } - // } - // } - // - // fieldset(.class("fieldset")) { - // legend(.class("fieldset-legend")) { "Password" } - // label(.class("input validator")) { - // SVG(.key) - // input( - // .type(.password), .placeholder("Password"), .required, - // .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"), - // .init(name: "minlength", value: "8") - // ) - // } - // if style == .signup { - // label(.class("input validator")) { - // SVG(.key) - // input( - // .type(.password), .placeholder("Confirm Password"), .required, - // .init(name: "pattern", value: "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"), - // .init(name: "minlength", value: "8") - // ) - // } - // } - // div(.class("validator-hint hidden")) { - // p { - // "Must be more than 8 characters, including" - // br() - // "At least one number" - // br() - // "At least one lowercase letter" - // br() - // "At least one uppercase letter" - // } - // } - // } - // - // SubmitButton(title: style.title) - // } - // } - // } } } diff --git a/Sources/ViewController/Views/User/SignupForm.swift b/Sources/ViewController/Views/User/SignupForm.swift deleted file mode 100644 index e69de29..0000000 diff --git a/output.css b/output.css index 242f0e1..9f8e300 100644 --- a/output.css +++ b/output.css @@ -16,6 +16,7 @@ --color-indigo-600: oklch(51.1% 0.262 276.966); --color-slate-300: oklch(86.9% 0.022 252.894); --color-slate-900: oklch(20.8% 0.042 265.755); + --color-gray-100: oklch(96.7% 0.003 264.542); --color-gray-200: oklch(92.8% 0.006 264.531); --color-gray-300: oklch(87.2% 0.01 258.338); --color-gray-400: oklch(70.7% 0.022 261.325); @@ -23,6 +24,16 @@ --color-black: #000; --color-white: #fff; --spacing: 0.25rem; + --container-xs: 20rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); --text-xl: 1.25rem; --text-xl--line-height: calc(1.75 / 1.25); --text-2xl: 1.5rem; @@ -31,9 +42,21 @@ --text-3xl--line-height: calc(2.25 / 1.875); --text-4xl: 2.25rem; --text-4xl--line-height: calc(2.5 / 2.25); + --text-5xl: 3rem; + --text-5xl--line-height: 1; + --font-weight-medium: 500; + --font-weight-semibold: 600; --font-weight-bold: 700; + --leading-tight: 1.25; + --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); } @@ -255,6 +278,272 @@ } } } + .menu { + @layer daisyui.l1.l2.l3 { + display: flex; + width: fit-content; + flex-direction: column; + flex-wrap: wrap; + padding: calc(0.25rem * 2); + --menu-active-fg: var(--color-neutral-content); + --menu-active-bg: var(--color-neutral); + font-size: 0.875rem; + :where(li ul) { + position: relative; + margin-inline-start: calc(0.25rem * 4); + padding-inline-start: calc(0.25rem * 2); + white-space: nowrap; + &:before { + position: absolute; + inset-inline-start: calc(0.25rem * 0); + top: calc(0.25rem * 3); + bottom: calc(0.25rem * 3); + background-color: var(--color-base-content); + opacity: 10%; + width: var(--border); + content: ""; + } + } + :where(li > .menu-dropdown:not(.menu-dropdown-show)) { + display: none; + } + :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: calc(0.25rem * 2); + border-radius: var(--radius-field); + padding-inline: calc(0.25rem * 3); + padding-block: calc(0.25rem * 1.5); + text-align: start; + transition-property: color, background-color, box-shadow; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + grid-auto-columns: minmax(auto, max-content) auto max-content; + text-wrap: balance; + user-select: none; + } + :where(li > details > summary) { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + &::-webkit-details-marker { + display: none; + } + } + :where(li > details > summary), :where(li > .menu-dropdown-toggle) { + &:after { + justify-self: flex-end; + display: block; + height: 0.375rem; + width: 0.375rem; + rotate: -135deg; + translate: 0 -1px; + transition-property: rotate, translate; + transition-duration: 0.2s; + content: ""; + transform-origin: 50% 50%; + box-shadow: 2px 2px inset; + pointer-events: none; + } + } + details { + overflow: hidden; + interpolate-size: allow-keywords; + } + details::details-content { + block-size: 0; + @media (prefers-reduced-motion: no-preference) { + transition-behavior: allow-discrete; + transition-property: block-size, content-visibility; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + } + details[open]::details-content { + block-size: auto; + } + :where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { + rotate: 45deg; + translate: 0 1px; + } + :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn) { + &.menu-focus, &:focus-visible { + cursor: pointer; + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + color: var(--color-base-content); + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + } + :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover ) { + cursor: pointer; + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + box-shadow: 0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset; + } + :where(li:empty) { + background-color: var(--color-base-content); + opacity: 10%; + margin: 0.5rem 1rem; + height: 1px; + } + :where(li) { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; + .badge { + justify-self: flex-end; + } + & > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + color: var(--menu-active-fg); + background-color: var(--menu-active-bg); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + &:not(&:active) { + box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg); + } + } + &.menu-disabled { + pointer-events: none; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } + } + .dropdown:focus-within { + .menu-dropdown-toggle:after { + rotate: 45deg; + translate: 0 1px; + } + } + .dropdown-content { + margin-top: calc(0.25rem * 2); + padding: calc(0.25rem * 2); + &:before { + display: none; + } + } + } + } + .dropdown { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-block; + position-area: var(--anchor-v, bottom) var(--anchor-h, span-right); + & > *:not(:has(~ [class*="dropdown-content"])):focus { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + .dropdown-content { + position: absolute; + } + &.dropdown-close .dropdown-content, &:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within) .dropdown-content, &.dropdown-hover:not(:hover) [tabindex]:first-child:focus:not(:focus-visible) ~ .dropdown-content { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + &[popover], .dropdown-content { + z-index: 999; + @media (prefers-reduced-motion: no-preference) { + animation: dropdown 0.2s; + transition-property: opacity, scale, display; + transition-behavior: allow-discrete; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + } + @starting-style { + &[popover], .dropdown-content { + scale: 95%; + opacity: 0; + } + } + &:not(.dropdown-close) { + &.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within { + > [tabindex]:first-child { + pointer-events: none; + } + .dropdown-content { + opacity: 100%; + scale: 100%; + } + } + &.dropdown-hover:hover { + .dropdown-content { + opacity: 100%; + scale: 100%; + } + } + } + &:is(details) { + summary { + &::-webkit-details-marker { + display: none; + } + } + } + &:where([popover]) { + background: #0000; + } + &[popover] { + position: fixed; + color: inherit; + @supports not (position-area: bottom) { + margin: auto; + &.dropdown-close, &.dropdown-open:not(:popover-open) { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + &::backdrop { + background-color: color-mix(in oklab, #000 30%, #0000); + } + } + &.dropdown-close, &:not(.dropdown-open, :popover-open) { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + } + } + } .btn { :where(&) { @layer daisyui.l1.l2.l3 { @@ -387,6 +676,210 @@ } } } + .btn-disabled { + @layer daisyui.l1.l2 { + &:not(.btn-link, .btn-ghost) { + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + box-shadow: none; + } + pointer-events: none; + --btn-border: #0000; + --btn-noise: none; + --btn-fg: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000); + } + } + } + .validator-hint { + @layer daisyui.l1.l2.l3 { + visibility: hidden; + margin-top: calc(0.25rem * 2); + font-size: 0.75rem; + } + } + .validator { + @layer daisyui.l1.l2.l3 { + &:user-valid, &:has(:user-valid) { + &, &:focus, &:checked, &[aria-checked="true"], &:focus-within { + --input-color: var(--color-success); + } + } + &:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) { + &, &:focus, &:checked, &[aria-checked="true"], &:focus-within { + --input-color: var(--color-error); + } + & ~ .validator-hint { + visibility: visible; + color: var(--color-error); + } + } + } + &:user-invalid, &:has(:user-invalid), &[aria-invalid]:not([aria-invalid="false"]), &:has([aria-invalid]:not([aria-invalid="false"])) { + & ~ .validator-hint { + display: revert-layer; + } + } + } + .toast { + @layer daisyui.l1.l2.l3 { + position: fixed; + inset-inline-start: auto; + inset-inline-end: calc(0.25rem * 4); + top: auto; + bottom: calc(0.25rem * 4); + display: flex; + flex-direction: column; + gap: calc(0.25rem * 2); + background-color: transparent; + translate: var(--toast-x, 0) var(--toast-y, 0); + width: max-content; + max-width: calc(100vw - 2rem); + & > * { + @media (prefers-reduced-motion: no-preference) { + animation: toast 0.25s ease-out; + } + } + } + } + .toggle { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid currentColor; + color: var(--input-color); + position: relative; + display: inline-grid; + flex-shrink: 0; + cursor: pointer; + appearance: none; + place-content: center; + vertical-align: middle; + webkit-user-select: none; + user-select: none; + grid-template-columns: 0fr 1fr 1fr; + --radius-selector-max: calc( + var(--radius-selector) + var(--radius-selector) + var(--radius-selector) + ); + border-radius: calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) ); + padding: var(--toggle-p); + box-shadow: 0 1px currentColor inset; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset; + } + transition: color 0.3s, grid-template-columns 0.2s; + --input-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --input-color: color-mix(in oklab, var(--color-base-content) 50%, #0000); + } + --toggle-p: calc(var(--size) * 0.125); + --size: calc(var(--size-selector, 0.25rem) * 6); + width: calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2); + height: var(--size); + > * { + z-index: 1; + grid-column: span 1 / span 1; + grid-column-start: 2; + grid-row-start: 1; + height: 100%; + cursor: pointer; + appearance: none; + background-color: transparent; + padding: calc(0.25rem * 0.5); + transition: opacity 0.2s, rotate 0.4s; + border: none; + &:focus { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + &:nth-child(2) { + color: var(--color-base-100); + rotate: 0deg; + } + &:nth-child(3) { + color: var(--color-base-100); + opacity: 0%; + rotate: -15deg; + } + } + &:has(:checked) { + > :nth-child(2) { + opacity: 0%; + rotate: 15deg; + } + > :nth-child(3) { + opacity: 100%; + rotate: 0deg; + } + } + &:before { + position: relative; + inset-inline-start: calc(0.25rem * 0); + grid-column-start: 2; + grid-row-start: 1; + aspect-ratio: 1 / 1; + height: 100%; + border-radius: var(--radius-selector); + background-color: currentcolor; + translate: 0; + --tw-content: ""; + content: var(--tw-content); + transition: background-color 0.1s, translate 0.2s, inset-inline-start 0.2s; + box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px currentColor; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000); + } + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + } + @media (forced-colors: active) { + &:before { + outline-style: var(--tw-outline-style); + outline-width: 1px; + outline-offset: calc(1px * -1); + } + } + @media print { + &:before { + outline: 0.25rem solid; + outline-offset: -1rem; + } + } + &:focus-visible, &:has(:focus-visible) { + outline: 2px solid currentColor; + outline-offset: 2px; + } + &:checked, &[aria-checked="true"], &:has(> input:checked) { + grid-template-columns: 1fr 1fr 0fr; + background-color: var(--color-base-100); + --input-color: var(--color-base-content); + &:before { + background-color: currentcolor; + } + @starting-style { + &:before { + opacity: 0; + } + } + } + &:indeterminate { + grid-template-columns: 0.5fr 1fr 0.5fr; + } + &:disabled { + cursor: not-allowed; + opacity: 30%; + &:before { + background-color: transparent; + border: var(--border) solid currentColor; + } + } + } + } .input { @layer daisyui.l1.l2.l3 { cursor: text; @@ -509,6 +1002,512 @@ } } } + .table { + @layer daisyui.l1.l2.l3 { + font-size: 0.875rem; + position: relative; + width: 100%; + border-collapse: separate; + --tw-border-spacing-x: calc(0.25rem * 0); + --tw-border-spacing-y: calc(0.25rem * 0); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + border-radius: var(--radius-box); + text-align: left; + &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { + text-align: right; + } + tr.row-hover { + &, &:nth-child(even) { + &:hover { + @media (hover: hover) { + background-color: var(--color-base-200); + } + } + } + } + :where(th, td) { + padding-inline: calc(0.25rem * 4); + padding-block: calc(0.25rem * 3); + vertical-align: middle; + } + :where(thead, tfoot) { + white-space: nowrap; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 60%, transparent); + } + font-size: 0.875rem; + font-weight: 600; + } + :where(tfoot tr:first-child :is(td, th)) { + border-top: var(--border) solid var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + border-top: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000); + } + } + :where(.table-pin-rows thead tr) { + position: sticky; + top: calc(0.25rem * 0); + z-index: 1; + background-color: var(--color-base-100); + } + :where(.table-pin-rows tfoot tr) { + position: sticky; + bottom: calc(0.25rem * 0); + z-index: 1; + background-color: var(--color-base-100); + } + :where(.table-pin-cols tr th) { + position: sticky; + right: calc(0.25rem * 0); + left: calc(0.25rem * 0); + background-color: var(--color-base-100); + } + :where(thead tr :is(td, th), tbody tr:not(:last-child) :is(td, th)) { + border-bottom: var(--border) solid var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000); + } + } + } + } + .select { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid #0000; + position: relative; + display: inline-flex; + flex-shrink: 1; + appearance: none; + align-items: center; + gap: calc(0.25rem * 1.5); + background-color: var(--color-base-100); + padding-inline-start: calc(0.25rem * 3); + padding-inline-end: calc(0.25rem * 7); + vertical-align: middle; + width: clamp(3rem, 20rem, 100%); + height: var(--size); + font-size: 0.875rem; + touch-action: manipulation; + border-start-start-radius: var(--join-ss, var(--radius-field)); + border-start-end-radius: var(--join-se, var(--radius-field)); + border-end-start-radius: var(--join-es, var(--radius-field)); + border-end-end-radius: var(--join-ee, var(--radius-field)); + background-image: linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%); + background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, 4px 4px; + background-repeat: no-repeat; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + } + border-color: var(--input-color); + --input-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + --size: calc(var(--size-field, 0.25rem) * 10); + [dir="rtl"] & { + background-position: calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%); + &::picker(select), select::picker(select) { + translate: 0.5rem 0; + } + } + &[multiple] { + height: auto; + overflow: auto; + padding-block: calc(0.25rem * 3); + padding-inline-end: calc(0.25rem * 3); + background-image: none; + } + select { + margin-inline-start: calc(0.25rem * -3); + margin-inline-end: calc(0.25rem * -7); + width: calc(100% + 2.75rem); + appearance: none; + padding-inline-start: calc(0.25rem * 3); + padding-inline-end: calc(0.25rem * 7); + height: calc(100% - calc(var(--border) * 2)); + align-items: center; + background: inherit; + border-radius: inherit; + border-style: none; + &:focus, &:focus-within { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + &:not(:last-child) { + margin-inline-end: calc(0.25rem * -5.5); + background-image: none; + } + } + &:focus, &:focus-within { + --input-color: var(--color-base-content); + box-shadow: 0 1px var(--input-color); + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000); + } + outline: 2px solid var(--input-color); + outline-offset: 2px; + isolation: isolate; + } + &:has(> select[disabled]), &:is(:disabled, [disabled]), fieldset:disabled & { + cursor: not-allowed; + border-color: var(--color-base-200); + background-color: var(--color-base-200); + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 40%, transparent); + } + &::placeholder { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } + } + &:has(> select[disabled]) > select[disabled] { + cursor: not-allowed; + } + &, & select { + @supports (appearance: base-select) { + appearance: base-select; + } + @supports (appearance: base-select) { + &::picker(select) { + appearance: base-select; + } + } + &::picker(select) { + color: inherit; + max-height: min(24rem, 70dvh); + margin-inline: 0.5rem; + translate: -0.5rem 0; + border: var(--border) solid var(--color-base-200); + margin-block: calc(0.25rem * 2); + border-radius: var(--radius-box); + padding: calc(0.25rem * 2); + background-color: inherit; + box-shadow: 0 2px calc(var(--depth) * 3px) -2px oklch(0% 0 0/0.2); + box-shadow: 0 20px 25px -5px rgb(0 0 0 / calc(var(--depth) * 0.1)), 0 8px 10px -6px rgb(0 0 0 / calc(var(--depth) * 0.1)); + } + &::picker-icon { + display: none; + } + optgroup { + padding-top: 0.5em; + option { + &:nth-child(1) { + margin-top: 0.5em; + } + } + } + option { + border-radius: var(--radius-field); + padding-inline: calc(0.25rem * 3); + padding-block: calc(0.25rem * 1.5); + transition-property: color, background-color; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + white-space: normal; + &:not(:disabled) { + &:hover, &:focus-visible { + cursor: pointer; + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + &:active { + background-color: var(--color-neutral); + color: var(--color-neutral-content); + box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--color-neutral); + } + } + } + } + } + } + .checkbox { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid var(--input-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + border: var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000)); + } + position: relative; + display: inline-block; + flex-shrink: 0; + cursor: pointer; + appearance: none; + border-radius: var(--radius-selector); + padding: calc(0.25rem * 1); + vertical-align: middle; + color: var(--color-base-content); + box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000; + transition: background-color 0.2s, box-shadow 0.2s; + --size: calc(var(--size-selector, 0.25rem) * 6); + width: var(--size); + height: var(--size); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + &:before { + --tw-content: ""; + content: var(--tw-content); + display: block; + width: 100%; + height: 100%; + rotate: 45deg; + background-color: currentcolor; + opacity: 0%; + transition: clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s; + transition-delay: 0.1s; + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%); + box-shadow: 0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + font-size: 1rem; + line-height: 0.75; + } + &:focus-visible { + outline: 2px solid var(--input-color, currentColor); + outline-offset: 2px; + } + &:checked, &[aria-checked="true"] { + background-color: var(--input-color, #0000); + box-shadow: 0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)); + &:before { + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%); + opacity: 100%; + } + @media (forced-colors: active) { + &:before { + rotate: 0deg; + background-color: transparent; + --tw-content: "✔︎"; + clip-path: none; + } + } + @media print { + &:before { + rotate: 0deg; + background-color: transparent; + --tw-content: "✔︎"; + clip-path: none; + } + } + } + &:indeterminate { + background-color: var( --input-color, var(--color-base-content) ); + @supports (color: color-mix(in lab, red, red)) { + background-color: var( --input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000) ); + } + &:before { + rotate: 0deg; + opacity: 100%; + translate: 0 -35%; + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%); + } + } + } + &:disabled { + @layer daisyui.l1.l2 { + cursor: not-allowed; + opacity: 20%; + } + } + } + .radio { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-block; + flex-shrink: 0; + cursor: pointer; + appearance: none; + border-radius: calc(infinity * 1px); + padding: calc(0.25rem * 1); + vertical-align: middle; + border: var(--border) solid var(--input-color, currentColor); + @supports (color: color-mix(in lab, red, red)) { + border: var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000)); + } + box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset; + --size: calc(var(--size-selector, 0.25rem) * 6); + width: var(--size); + height: var(--size); + color: var(--input-color, currentColor); + &:before { + display: block; + width: 100%; + height: 100%; + border-radius: calc(infinity * 1px); + --tw-content: ""; + content: var(--tw-content); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + } + &:focus-visible { + outline: 2px solid currentColor; + } + &:checked, &[aria-checked="true"] { + border-color: currentcolor; + background-color: var(--color-base-100); + @media (prefers-reduced-motion: no-preference) { + animation: radio 0.2s ease-out; + } + &:before { + background-color: currentcolor; + box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)); + } + @media (forced-colors: active) { + &:before { + outline-style: var(--tw-outline-style); + outline-width: 1px; + outline-offset: calc(1px * -1); + } + } + @media print { + &:before { + outline: 0.25rem solid; + outline-offset: -1rem; + } + } + } + } + &:disabled { + @layer daisyui.l1.l2 { + cursor: not-allowed; + opacity: 20%; + } + } + } + .rating { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-flex; + vertical-align: middle; + & input { + border: none; + appearance: none; + } + :where(*) { + height: calc(0.25rem * 6); + width: calc(0.25rem * 6); + border-radius: 0; + background-color: var(--color-base-content); + opacity: 20%; + @media (prefers-reduced-motion: no-preference) { + animation: rating 0.25s ease-out; + } + &:is(input) { + cursor: pointer; + } + } + & .rating-hidden { + width: calc(0.25rem * 2); + background-color: transparent; + } + input[type="radio"]:checked { + background-image: none; + } + * { + &:checked, &[aria-checked="true"], &[aria-current="true"], &:has(~ *:checked, ~ *[aria-checked="true"], ~ *[aria-current="true"]) { + opacity: 100%; + } + &:focus-visible { + scale: 1.1; + @media (prefers-reduced-motion: no-preference) { + transition: scale 0.2s ease-out; + } + } + } + & *:active:focus { + animation: none; + scale: 1.1; + } + } + @layer daisyui.l1.l2 { + &.rating-xs :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 4); + height: calc(0.25rem * 4); + } + &.rating-sm :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 5); + height: calc(0.25rem * 5); + } + &.rating-md :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 6); + height: calc(0.25rem * 6); + } + &.rating-lg :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 7); + height: calc(0.25rem * 7); + } + &.rating-xl :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 8); + height: calc(0.25rem * 8); + } + } + } + .progress { + @layer daisyui.l1.l2.l3 { + position: relative; + height: calc(0.25rem * 2); + width: 100%; + appearance: none; + overflow: hidden; + border-radius: var(--radius-box); + background-color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, currentcolor 20%, transparent); + } + color: var(--color-base-content); + &:indeterminate { + background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% ); + background-size: 200%; + background-position-x: 15%; + @media (prefers-reduced-motion: no-preference) { + animation: progress 5s ease-in-out infinite; + } + @supports (-moz-appearance: none) { + &::-moz-progress-bar { + background-color: transparent; + @media (prefers-reduced-motion: no-preference) { + animation: progress 5s ease-in-out infinite; + background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% ); + background-size: 200%; + background-position-x: 15%; + } + } + } + } + @supports (-moz-appearance: none) { + &::-moz-progress-bar { + border-radius: var(--radius-box); + background-color: currentcolor; + } + } + @supports (-webkit-appearance: none) { + &::-webkit-progress-bar { + border-radius: var(--radius-box); + background-color: transparent; + } + &::-webkit-progress-value { + border-radius: var(--radius-box); + background-color: currentColor; + } + } + } + } .fixed { position: fixed; } @@ -518,6 +1517,18 @@ .sticky { position: sticky; } + .tooltip-left { + @layer daisyui.l1.l2 { + > .tooltip-content, &[data-tip]:before { + transform: translateX(calc(var(--tt-pos, 0.25rem) - 0.25rem)) translateY(-50%); + inset: 50% var(--tt-off) auto auto; + } + &:after { + transform: translateX(var(--tt-pos, 0.25rem)) translateY(-50%) rotate(-90deg); + inset: 50% calc(var(--tt-tail) + 1px) auto auto; + } + } + } .top-0 { top: calc(var(--spacing) * 0); } @@ -527,12 +1538,84 @@ .left-\[25vw\] { left: 25vw; } + .textarea { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid #0000; + min-height: calc(0.25rem * 20); + flex-shrink: 1; + appearance: none; + border-radius: var(--radius-field); + background-color: var(--color-base-100); + padding-block: calc(0.25rem * 2); + vertical-align: middle; + width: clamp(3rem, 20rem, 100%); + padding-inline-start: 0.75rem; + padding-inline-end: 0.75rem; + font-size: max(var(--font-size, 0.875rem), 0.875rem); + touch-action: manipulation; + border-color: var(--input-color); + box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + } + --input-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + textarea { + appearance: none; + background-color: transparent; + border: none; + &:focus, &:focus-within { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + } + &:focus, &:focus-within { + --input-color: var(--color-base-content); + box-shadow: 0 1px var(--input-color); + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000); + } + outline: 2px solid var(--input-color); + outline-offset: 2px; + isolation: isolate; + } + @media (pointer: coarse) { + @supports (-webkit-touch-callout: none) { + &:focus, &:focus-within { + --font-size: 1rem; + } + } + } + &:has(> textarea[disabled]), &:is(:disabled, [disabled]) { + cursor: not-allowed; + border-color: var(--color-base-200); + background-color: var(--color-base-200); + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 40%, transparent); + } + &::placeholder { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } + box-shadow: none; + } + &:has(> textarea[disabled]) > textarea[disabled] { + cursor: not-allowed; + } + } + } .z-50 { z-index: 50; } - .col-span-5 { - grid-column: span 5 / span 5; - } .container { width: 100%; @media (width >= 40rem) { @@ -554,9 +1637,6 @@ .m-4 { margin: calc(var(--spacing) * 4); } - .m-10 { - margin: calc(var(--spacing) * 10); - } .filter { @layer daisyui.l1.l2.l3 { display: flex; @@ -644,8 +1724,20 @@ } } } - .mb-2 { - margin-bottom: calc(var(--spacing) * 2); + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .fieldset-legend { + @layer daisyui.l1.l2.l3 { + margin-bottom: calc(0.25rem * -1); + display: flex; + align-items: center; + justify-content: space-between; + gap: calc(0.25rem * 2); + padding-block: calc(0.25rem * 2); + color: var(--color-base-content); + font-weight: 600; + } } .status { @layer daisyui.l1.l2.l3 { @@ -672,6 +1764,94 @@ } } } + .badge { + @layer daisyui.l1.l2.l3 { + display: inline-flex; + align-items: center; + justify-content: center; + gap: calc(0.25rem * 2); + border-radius: var(--radius-selector); + vertical-align: middle; + color: var(--badge-fg); + border: var(--border) solid var(--badge-color, var(--color-base-200)); + font-size: 0.875rem; + width: fit-content; + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + background-color: var(--badge-bg); + --badge-bg: var(--badge-color, var(--color-base-100)); + --badge-fg: var(--color-base-content); + --size: calc(var(--size-selector, 0.25rem) * 6); + height: var(--size); + padding-inline: calc(var(--size) / 2 - var(--border)); + } + } + .fieldset { + @layer daisyui.l1.l2.l3 { + display: grid; + gap: calc(0.25rem * 1.5); + padding-block: calc(0.25rem * 1); + font-size: 0.75rem; + grid-template-columns: 1fr; + grid-auto-rows: max-content; + } + } + .prose { + :root & { + --tw-prose-body: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-body: color-mix(in oklab, var(--color-base-content) 80%, #0000); + } + --tw-prose-headings: var(--color-base-content); + --tw-prose-lead: var(--color-base-content); + --tw-prose-links: var(--color-base-content); + --tw-prose-bold: var(--color-base-content); + --tw-prose-counters: var(--color-base-content); + --tw-prose-bullets: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-bullets: color-mix(in oklab, var(--color-base-content) 50%, #0000); + } + --tw-prose-hr: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-hr: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + --tw-prose-quotes: var(--color-base-content); + --tw-prose-quote-borders: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-quote-borders: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + --tw-prose-captions: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-captions: color-mix(in oklab, var(--color-base-content) 50%, #0000); + } + --tw-prose-code: var(--color-base-content); + --tw-prose-pre-code: var(--color-neutral-content); + --tw-prose-pre-bg: var(--color-neutral); + --tw-prose-th-borders: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-th-borders: color-mix(in oklab, var(--color-base-content) 50%, #0000); + } + --tw-prose-td-borders: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-td-borders: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + --tw-prose-kbd: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --tw-prose-kbd: color-mix(in oklab, var(--color-base-content) 80%, #0000); + } + :where(code):not(pre > code) { + background-color: var(--color-base-200); + border-radius: var(--radius-selector); + border: var(--border) solid var(--color-base-300); + padding-inline: 0.5em; + padding-block: 0.2em; + font-weight: inherit; + &:before, &:after { + display: none; + } + } + } + } .contents { display: contents; } @@ -684,23 +1864,84 @@ .hidden { display: none; } + .table { + display: table; + } + .h-\[1em\] { + height: 1em; + } .h-screen { height: 100vh; } + .w-1 { + width: calc(var(--spacing) * 1); + } .w-1\/2 { width: calc(1/2 * 100%); } + .w-\[40px\] { + width: 40px; + } .w-full { width: 100%; } + .w-xl { + width: var(--container-xl); + } .max-w-\[280px\] { max-width: 280px; } .flex-none { flex: none; } - .grid-cols-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); + .flex-shrink { + flex-shrink: 1; + } + .border-collapse { + border-collapse: collapse; + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .skeleton { + @layer daisyui.l1.l2.l3 { + border-radius: var(--radius-box); + background-color: var(--color-base-300); + @media (prefers-reduced-motion: reduce) { + transition-duration: 15s; + } + will-change: background-position; + background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% ); + background-size: 200% auto; + background-position-x: -50%; + @media (prefers-reduced-motion: no-preference) { + animation: skeleton 1.8s ease-in-out infinite; + } + } + } + .link { + @layer daisyui.l1.l2.l3 { + cursor: pointer; + text-decoration-line: underline; + &:focus { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + &:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; + } + } + } + .resize { + resize: both; + } + .grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); } .flex-col { flex-direction: column; @@ -708,12 +1949,18 @@ .flex-row { flex-direction: row; } + .flex-wrap { + flex-wrap: wrap; + } .items-center { align-items: center; } .justify-between { justify-content: space-between; } + .justify-center { + justify-content: center; + } .gap-4 { gap: calc(var(--spacing) * 4); } @@ -745,6 +1992,15 @@ margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); } } + .overflow-x-auto { + overflow-x: auto; + } + .rounded-box { + border-radius: var(--radius-box); + } + .rounded-box { + border-radius: var(--radius-box); + } .rounded-lg { border-radius: var(--radius-lg); } @@ -767,14 +2023,48 @@ border-bottom-style: var(--tw-border-style); border-bottom-width: 1px; } + .badge-outline { + @layer daisyui.l1.l2 { + color: var(--badge-color); + --badge-bg: #0000; + background-image: none; + border-color: currentColor; + } + } + .border-base-300 { + border-color: var(--color-base-300); + } .border-gray-200 { border-color: var(--color-gray-200); } .border-gray-400 { border-color: var(--color-gray-400); } - .bg-blue-400 { - background-color: var(--color-blue-400); + .table-zebra { + @layer daisyui.l1.l2 { + tbody { + tr { + &:where(:nth-child(even)) { + background-color: var(--color-base-200); + :where(.table-pin-cols tr th) { + background-color: var(--color-base-200); + } + } + &.row-hover { + &, &:where(:nth-child(even)) { + &:hover { + @media (hover: hover) { + background-color: var(--color-base-300); + } + } + } + } + } + } + } + } + .bg-base-200 { + background-color: var(--color-base-200); } .bg-blue-500 { background-color: var(--color-blue-500); @@ -782,15 +2072,15 @@ .bg-gray-200 { background-color: var(--color-gray-200); } - .bg-green-400 { - background-color: var(--color-green-400); - } .bg-red-500 { background-color: var(--color-red-500); } .bg-white { background-color: var(--color-white); } + .mask-repeat { + mask-repeat: repeat; + } .p-4 { padding: calc(var(--spacing) * 4); } @@ -800,6 +2090,12 @@ .px-4 { padding-inline: calc(var(--spacing) * 4); } + .px-6 { + padding-inline: calc(var(--spacing) * 6); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } .py-1\.5 { padding-block: calc(var(--spacing) * 1.5); } @@ -809,6 +2105,9 @@ .py-4 { padding-block: calc(var(--spacing) * 4); } + .py-10 { + padding-block: calc(var(--spacing) * 10); + } .ps-2 { padding-inline-start: calc(var(--spacing) * 2); } @@ -837,12 +2136,34 @@ font-size: var(--text-xl); line-height: var(--tw-leading, var(--text-xl--line-height)); } + .badge-xl { + @layer daisyui.l1.l2 { + --size: calc(var(--size-selector, 0.25rem) * 8); + font-size: 1.125rem; + } + } .font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); } - .text-blue-400 { - color: var(--color-blue-400); + .text-wrap { + text-wrap: wrap; + } + .link-primary { + @layer daisyui.l1.l2 { + color: var(--color-primary); + @media (hover: hover) { + &:hover { + color: var(--color-primary); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-primary) 80%, #000); + } + } + } + } + } + .text-error { + color: var(--color-error); } .text-gray-400 { color: var(--color-gray-400); @@ -850,22 +2171,63 @@ .text-gray-800 { color: var(--color-gray-800); } - .text-green-400 { - color: var(--color-green-400); - } - .text-red-500 { - color: var(--color-red-500); + .text-info { + color: var(--color-info); } .text-slate-900 { color: var(--color-slate-900); } + .text-success { + color: var(--color-success); + } .text-white { color: var(--color-white); } + .lowercase { + text-transform: lowercase; + } + .uppercase { + text-transform: uppercase; + } + .btn-link { + .prose :where(&):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + text-decoration-line: none; + } + @layer daisyui.l1 { + text-decoration-line: underline; + outline-color: currentcolor; + --btn-border: #0000; + --btn-bg: #0000; + --btn-noise: none; + --btn-shadow: ""; + &:not(.btn-disabled, .btn:disabled, .btn[disabled]) { + --btn-fg: var(--btn-color, var(--color-primary)); + } + &:is(.btn-active, :hover, :active:focus, :focus-visible) { + --btn-border: #0000; + --btn-bg: #0000; + } + } + } + .prose { + & :where(.btn-link):not(:where([class~="not-prose"], [class~="not-prose"] *)) { + text-decoration-line: none; + } + } + .underline { + text-decoration-line: underline; + } + .opacity-50 { + opacity: 50%; + } .shadow-lg { --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } .outline-1 { outline-style: var(--tw-outline-style); outline-width: 1px; @@ -879,6 +2241,15 @@ .filter { filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } .btn-outline { @layer daisyui.l1 { &:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) { @@ -899,16 +2270,88 @@ } } } + .btn-soft { + @layer daisyui.l1 { + &:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) { + --btn-shadow: ""; + --btn-fg: var(--btn-color, var(--color-base-content)); + --btn-bg: var(--btn-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + --btn-bg: color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + ); + } + --btn-border: var(--btn-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + --btn-border: color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + ); + } + --btn-noise: none; + } + @media (hover: none) { + &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { + --btn-shadow: ""; + --btn-fg: var(--btn-color, var(--color-base-content)); + --btn-bg: var(--btn-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + --btn-bg: color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 8%, + var(--color-base-100) + ); + } + --btn-border: var(--btn-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + --btn-border: color-mix( + in oklab, + var(--btn-color, var(--color-base-content)) 10%, + var(--color-base-100) + ); + } + --btn-noise: none; + } + } + } + } + .badge-error { + @layer daisyui.l1.l2 { + --badge-color: var(--color-error); + --badge-fg: var(--color-error-content); + } + } + .badge-info { + @layer daisyui.l1.l2 { + --badge-color: var(--color-info); + --badge-fg: var(--color-info-content); + } + } + .badge-success { + @layer daisyui.l1.l2 { + --badge-color: var(--color-success); + --badge-fg: var(--color-success-content); + } + } + .btn-neutral { + @layer daisyui.l1.l2.l3 { + --btn-color: var(--color-neutral); + --btn-fg: var(--color-neutral-content); + } + } .btn-primary { @layer daisyui.l1.l2.l3 { --btn-color: var(--color-primary); --btn-fg: var(--color-primary-content); } } - .btn-success { + .btn-secondary { @layer daisyui.l1.l2.l3 { - --btn-color: var(--color-success); - --btn-fg: var(--color-success-content); + --btn-color: var(--color-secondary); + --btn-fg: var(--color-secondary-content); } } .invalid\:border-red-500 { @@ -1232,6 +2675,26 @@ background-position-x: -115%; } } +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} @property --tw-space-y-reverse { syntax: "*"; inherits: false; @@ -1374,9 +2837,18 @@ syntax: "*"; inherits: false; } +@property --tw-ease { + syntax: "*"; + inherits: false; +} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; --tw-space-y-reverse: 0; --tw-space-x-reverse: 0; --tw-border-style: solid; @@ -1409,6 +2881,7 @@ --tw-drop-shadow-color: initial; --tw-drop-shadow-alpha: 100%; --tw-drop-shadow-size: initial; + --tw-ease: initial; } } } diff --git a/swift-dev b/swift-dev index 73e4c3d..6b154ff 100755 --- a/swift-dev +++ b/swift-dev @@ -1,5 +1,5 @@ #!/usr/bin/env bash touch .build/browser-dev-sync -# browser-sync start --proxy localhost:8080 --ws -w --no-notify & +browser-sync start --proxy localhost:8080 --ws & watchexec -w Sources -e .swift -r 'swift build --product App && touch .build/browser-dev-sync' & watchexec -w .build/browser-dev-sync -r 'swift run App'