From 202ee1340b85e7236e7f3f925ceeab510a29e93f Mon Sep 17 00:00:00 2001 From: spinline Date: Sat, 31 Jan 2026 14:35:16 +0300 Subject: [PATCH] feat(frontend): rewrite frontend with minimal Transmission-like design using DaisyUI --- Cargo.lock | 155 -- frontend/Cargo.toml | 1 - frontend/index.html | 19 +- frontend/input.css | 162 +- frontend/package-lock.json | 11 + frontend/package.json | 1 + frontend/public/tailwind.css | 1517 +++++++++++++++---- frontend/src/app.rs | 245 +-- frontend/src/components/layout/mod.rs | 3 + frontend/src/components/layout/sidebar.rs | 65 + frontend/src/components/layout/statusbar.rs | 35 + frontend/src/components/layout/toolbar.rs | 48 + frontend/src/components/mod.rs | 7 +- frontend/src/components/modal.rs | 4 +- frontend/src/components/sidebar.rs | 52 - frontend/src/components/status_bar.rs | 20 - frontend/src/components/toolbar.rs | 34 - frontend/src/components/torrent/mod.rs | 1 + frontend/src/components/torrent/table.rs | 124 ++ frontend/src/components/torrent_table.rs | 51 - frontend/tailwind.config.js | 1 + 21 files changed, 1535 insertions(+), 1021 deletions(-) create mode 100644 frontend/src/components/layout/mod.rs create mode 100644 frontend/src/components/layout/sidebar.rs create mode 100644 frontend/src/components/layout/statusbar.rs create mode 100644 frontend/src/components/layout/toolbar.rs delete mode 100644 frontend/src/components/sidebar.rs delete mode 100644 frontend/src/components/status_bar.rs delete mode 100644 frontend/src/components/toolbar.rs create mode 100644 frontend/src/components/torrent/mod.rs create mode 100644 frontend/src/components/torrent/table.rs delete mode 100644 frontend/src/components/torrent_table.rs diff --git a/Cargo.lock b/Cargo.lock index 4e11116..b57c6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,15 +97,6 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - [[package]] name = "async-compression" version = "0.4.37" @@ -323,12 +314,6 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -[[package]] -name = "by_address" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" - [[package]] name = "byteorder" version = "1.5.0" @@ -659,12 +644,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "fast-srgb8" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" - [[package]] name = "find-msvc-tools" version = "0.1.8" @@ -711,7 +690,6 @@ dependencies = [ "serde_json", "shared", "tailwind_fuse", - "thaw", "uuid", "wasm-bindgen", "web-sys", @@ -1057,21 +1035,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icondata_ai" -version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf3a9c196a6a169f790639ecc8fdd4396660b1d53b905230bf0b364776a56fc" -dependencies = [ - "icondata_core", -] - -[[package]] -name = "icondata_core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c97be924215abd5e630d84e95a47c710138a6559b4c55039f4f33aa897fa859" - [[package]] name = "icu_collections" version = "2.1.1" @@ -1563,30 +1526,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" -[[package]] -name = "palette" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" -dependencies = [ - "approx", - "fast-srgb8", - "palette_derive", - "phf", -] - -[[package]] -name = "palette_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" -dependencies = [ - "by_address", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -1628,48 +1567,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -2245,12 +2142,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "slab" version = "0.4.11" @@ -2344,52 +2235,6 @@ dependencies = [ "nom", ] -[[package]] -name = "thaw" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2ea14de27ddd0ce167c69e78852122c5a9c755b3c083c8249e491c4a8821ce" -dependencies = [ - "cfg-if", - "chrono", - "icondata_ai", - "icondata_core", - "leptos", - "num-traits", - "palette", - "thaw_components", - "thaw_utils", - "uuid", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "thaw_components" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ee9d9598f7a5162845a83ef0ed3ccde52c23d987cf64433056b2fd4542161c" -dependencies = [ - "cfg-if", - "leptos", - "thaw_utils", - "uuid", - "web-sys", -] - -[[package]] -name = "thaw_utils" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5fe5c99ba6b6730ec533810dd4d92422b778d4fbaffa9b6d7fb705b28b3978" -dependencies = [ - "cfg-if", - "chrono", - "leptos", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "thiserror" version = "1.0.69" diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index ca6b0ab..c6f3d90 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -21,4 +21,3 @@ chrono = { version = "0.4", features = ["serde"] } web-sys = { version = "0.3", features = ["Window", "Storage"] } shared = { path = "../shared" } tailwind_fuse = "0.3.2" -thaw = "0.3" diff --git a/frontend/index.html b/frontend/index.html index 2ce83db..3cada68 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -20,21 +20,12 @@ diff --git a/frontend/input.css b/frontend/input.css index 84a0e6a..1dbdb3f 100644 --- a/frontend/input.css +++ b/frontend/input.css @@ -1,165 +1,11 @@ @import "tailwindcss"; +@config "./tailwind.config.js"; +@plugin "daisyui"; @layer base { html, - body, - button, - a, - [role="button"], - input, - label, - select, - summary, - textarea { - touch-action: manipulation; - } -} - -@layer base { - - :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 10% 3.9%; - - --radius: 0.5rem; - } - - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; - } - - .amoled { - --background: 0 0% 0%; - --foreground: 0 0% 98%; - --card: 0 0% 0%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 0%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 0%; - --secondary: 0 0% 9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 9%; - --muted-foreground: 0 0% 60%; - --accent: 0 0% 9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 12%; - --input: 0 0% 12%; - --ring: 0 0% 83.9%; - } - - * { - @apply border-border; - } -} - -@theme { - --color-background: hsl(var(--background)); - --color-foreground: hsl(var(--foreground)); - - --color-card: hsl(var(--card)); - --color-card-foreground: hsl(var(--card-foreground)); - - --color-popover: hsl(var(--popover)); - --color-popover-foreground: hsl(var(--popover-foreground)); - - --color-primary: hsl(var(--primary)); - --color-primary-foreground: hsl(var(--primary-foreground)); - - --color-secondary: hsl(var(--secondary)); - --color-secondary-foreground: hsl(var(--secondary-foreground)); - - --color-muted: hsl(var(--muted)); - --color-muted-foreground: hsl(var(--muted-foreground)); - - --color-accent: hsl(var(--accent)); - --color-accent-foreground: hsl(var(--accent-foreground)); - - --color-destructive: hsl(var(--destructive)); - --color-destructive-foreground: hsl(var(--destructive-foreground)); - - --color-border: hsl(var(--border)); - --color-input: hsl(var(--input)); - --color-ring: hsl(var(--ring)); - - --radius-lg: var(--radius); - --radius-md: calc(var(--radius) - 2px); - --radius-sm: calc(var(--radius) - 4px); - - --animate-accordion-down: accordion-down 0.2s ease-out; - --animate-accordion-up: accordion-up 0.2s ease-out; - - @keyframes accordion-down { - from { - height: 0 - } - - to { - height: var(--radix-accordion-content-height) - } - } - - @keyframes accordion-up { - from { - height: var(--radix-accordion-content-height) - } - - to { - height: 0 - } + body { + @apply h-full w-full overflow-hidden bg-base-100 text-base-content; } } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b1f3cf1..142c9aa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,7 @@ }, "devDependencies": { "autoprefixer": "^10.4.23", + "daisyui": "^5.5.1-beta.2", "postcss": "^8.5.6", "tailwindcss": "^4.1.18" } @@ -720,6 +721,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/daisyui": { + "version": "5.5.1-beta.2", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.1-beta.2.tgz", + "integrity": "sha512-MJgPmmXKW7G8Vvt/z096Til1vYsB7CCNms5FElU2nDcOxEnjhMTUyQnXgzLjo0C9XWuIXEnr62F95Kv2Udq1GQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 29397a4..de5d739 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "license": "ISC", "devDependencies": { "autoprefixer": "^10.4.23", + "daisyui": "^5.5.1-beta.2", "postcss": "^8.5.6", "tailwindcss": "^4.1.18" }, diff --git a/frontend/public/tailwind.css b/frontend/public/tailwind.css index 47ea9c8..d09f7d7 100644 --- a/frontend/public/tailwind.css +++ b/frontend/public/tailwind.css @@ -13,10 +13,11 @@ --color-red-900: oklch(39.6% 0.141 25.723); --color-yellow-500: oklch(79.5% 0.184 86.047); --color-green-500: oklch(72.3% 0.219 149.579); - --color-blue-500: oklch(62.3% 0.214 259.815); --color-gray-500: oklch(55.1% 0.027 264.364); + --color-black: #000; --color-white: #fff; --spacing: 0.25rem; + --container-xs: 20rem; --container-sm: 24rem; --text-xs: 0.75rem; --text-xs--line-height: calc(1 / 0.75); @@ -24,14 +25,14 @@ --text-sm--line-height: calc(1.25 / 0.875); --text-lg: 1.125rem; --text-lg--line-height: calc(1.75 / 1.125); - --text-2xl: 1.5rem; - --text-2xl--line-height: calc(2 / 1.5); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --tracking-wider: 0.05em; - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); + --radius-md: 0.375rem; + --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-2xl: 1rem; --blur-sm: 8px; @@ -40,21 +41,6 @@ --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); - --color-background: hsl(var(--background)); - --color-foreground: hsl(var(--foreground)); - --color-card: hsl(var(--card)); - --color-card-foreground: hsl(var(--card-foreground)); - --color-primary: hsl(var(--primary)); - --color-primary-foreground: hsl(var(--primary-foreground)); - --color-muted: hsl(var(--muted)); - --color-muted-foreground: hsl(var(--muted-foreground)); - --color-accent: hsl(var(--accent)); - --color-accent-foreground: hsl(var(--accent-foreground)); - --color-destructive: hsl(var(--destructive)); - --color-destructive-foreground: hsl(var(--destructive-foreground)); - --color-border: hsl(var(--border)); - --color-input: hsl(var(--input)); - --color-ring: hsl(var(--ring)); } } @layer base { @@ -206,78 +192,866 @@ } } @layer utilities { + .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; + } + } + } + } + .btn { + :where(&) { + @layer daisyui.l1.l2.l3 { + width: unset; + } + } + @layer daisyui.l1.l2.l3 { + display: inline-flex; + flex-shrink: 0; + cursor: pointer; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + gap: calc(0.25rem * 1.5); + text-align: center; + vertical-align: middle; + outline-offset: 2px; + webkit-user-select: none; + user-select: none; + padding-inline: var(--btn-p); + color: var(--btn-fg); + --tw-prose-links: var(--btn-fg); + height: var(--size); + font-size: var(--fontsize, 0.875rem); + font-weight: 600; + outline-color: var(--btn-color, var(--color-base-content)); + transition-property: color, background-color, border-color, box-shadow; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 0.2s; + 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-color: var(--btn-bg); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--btn-noise); + border-width: var(--border); + border-style: solid; + border-color: var(--btn-border); + text-shadow: 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15)); + touch-action: manipulation; + box-shadow: 0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow); + --size: calc(var(--size-field, 0.25rem) * 10); + --btn-bg: var(--btn-color, var(--color-base-200)); + --btn-fg: var(--color-base-content); + --btn-p: 1rem; + --btn-border: var(--btn-bg); + @supports (color: color-mix(in lab, red, red)) { + --btn-border: color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%)); + } + --btn-shadow: 0 3px 2px -2px var(--btn-bg), + 0 4px 3px -2px var(--btn-bg); + @supports (color: color-mix(in lab, red, red)) { + --btn-shadow: 0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000), + 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000); + } + --btn-noise: var(--fx-noise); + @media (hover: hover) { + &:hover { + --btn-bg: var(--btn-color, var(--color-base-200)); + @supports (color: color-mix(in lab, red, red)) { + --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%); + } + } + } + &:focus-visible, &:has(:focus-visible) { + outline-width: 2px; + outline-style: solid; + isolation: isolate; + } + &:active:not(.btn-active) { + translate: 0 0.5px; + --btn-bg: var(--btn-color, var(--color-base-200)); + @supports (color: color-mix(in lab, red, red)) { + --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%); + } + --btn-border: var(--btn-color, var(--color-base-200)); + @supports (color: color-mix(in lab, red, red)) { + --btn-border: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%); + } + --btn-shadow: 0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0); + } + &:is(input[type="checkbox"], input[type="radio"]) { + appearance: none; + &::after { + --tw-content: attr(aria-label); + content: var(--tw-content); + } + } + &:where(input:checked:not(.filter .btn)) { + --btn-color: var(--color-primary); + --btn-fg: var(--color-primary-content); + isolation: isolate; + } + } + &: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); + } + } + } + &[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); + } + } + } + } .\!visible { visibility: visible !important; } .visible { visibility: visible; } + .input { + @layer daisyui.l1.l2.l3 { + cursor: text; + border: var(--border) solid #0000; + position: relative; + display: inline-flex; + flex-shrink: 1; + appearance: none; + align-items: center; + gap: calc(0.25rem * 2); + background-color: var(--color-base-100); + padding-inline: calc(0.25rem * 3); + vertical-align: middle; + white-space: nowrap; + width: clamp(3rem, 20rem, 100%); + height: var(--size); + font-size: max(var(--font-size, 0.875rem), 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)); + 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; + } + --size: calc(var(--size-field, 0.25rem) * 10); + --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); + } + &:where(input) { + display: inline-flex; + } + :where(input) { + display: inline-flex; + height: 100%; + width: 100%; + 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; + } + } + } + :where(input[type="url"]), :where(input[type="email"]) { + direction: ltr; + } + :where(input[type="date"]) { + display: inline-flex; + } + &: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; + z-index: 1; + } + @media (pointer: coarse) { + @supports (-webkit-touch-callout: none) { + &:focus, &:focus-within { + --font-size: 1rem; + } + } + } + &:has(> input[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); + } + } + box-shadow: none; + } + &:has(> input[disabled]) > input[disabled] { + cursor: not-allowed; + } + &::-webkit-date-and-time-value { + text-align: inherit; + } + &[type="number"] { + &::-webkit-inner-spin-button { + margin-block: calc(0.25rem * -3); + margin-inline-end: calc(0.25rem * -3); + } + } + &::-webkit-calendar-picker-indicator { + position: absolute; + inset-inline-end: 0.75em; + } + &:has(> input[type="date"]) { + :where(input[type="date"]) { + display: inline-flex; + webkit-appearance: none; + appearance: none; + } + input[type="date"]::-webkit-calendar-picker-indicator { + position: absolute; + inset-inline-end: 0.75em; + width: 1em; + height: 1em; + cursor: pointer; + } + } + } + } + .table { + @layer daisyui.l1.l2.l3 { + font-size: 0.875rem; + position: relative; + width: 100%; + 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) { + 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, tbody tr:not(:last-child)) { + 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); + } + } + } + } + .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%; + } + } + } + .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; + } + } + } + } .absolute { position: absolute; } .fixed { position: fixed; } - .sticky { - position: sticky; - } .inset-0 { inset: calc(var(--spacing) * 0); } - .top-0 { - top: calc(var(--spacing) * 0); - } - .z-10 { - z-index: 10; - } .z-\[100\] { z-index: 100; } .z-\[200\] { z-index: 200; } - .mx-2 { - margin-inline: calc(var(--spacing) * 2); + .input-sm { + @layer daisyui.l1.l2 { + --size: calc(var(--size-field, 0.25rem) * 8); + font-size: max(var(--font-size, 0.75rem), 0.75rem); + &[type="number"] { + &::-webkit-inner-spin-button { + margin-block: calc(0.25rem * -2); + margin-inline-end: calc(0.25rem * -3); + } + } + } } .my-1 { margin-block: calc(var(--spacing) * 1); } - .mr-2 { - margin-right: calc(var(--spacing) * 2); + .join-item { + &:where(*:not(:first-child, :disabled, [disabled], .btn-disabled)) { + margin-inline-start: calc(var(--border, 1px) * -1); + margin-block-start: 0; + } + &:where(*:is(:disabled, [disabled], .btn-disabled)) { + border-width: var(--border, 1px) 0 var(--border, 1px) var(--border, 1px); + } + } + .mt-auto { + margin-top: auto; } .mb-1 { margin-bottom: calc(var(--spacing) * 1); } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } .mb-4 { margin-bottom: calc(var(--spacing) * 4); } .mb-6 { margin-bottom: calc(var(--spacing) * 6); } + .ml-auto { + margin-left: auto; + } + .status { + @layer daisyui.l1.l2.l3 { + display: inline-block; + aspect-ratio: 1 / 1; + width: calc(0.25rem * 2); + height: calc(0.25rem * 2); + border-radius: var(--radius-selector); + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + background-position: center; + background-repeat: no-repeat; + vertical-align: middle; + color: color-mix(in srgb, #000 30%, transparent); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-black) 30%, transparent); + } + background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 ); + box-shadow: 0 2px 3px -1px currentColor; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000); + } + } + } + .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; + padding-inline: calc(0.25rem * 3 - var(--border)); + 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); + } + } + .join { + display: inline-flex; + align-items: stretch; + --join-ss: 0; + --join-se: 0; + --join-es: 0; + --join-ee: 0; + :where(.join-item) { + border-start-start-radius: var(--join-ss, 0); + border-start-end-radius: var(--join-se, 0); + border-end-start-radius: var(--join-es, 0); + border-end-end-radius: var(--join-ee, 0); + * { + --join-ss: var(--radius-field); + --join-se: var(--radius-field); + --join-es: var(--radius-field); + --join-ee: var(--radius-field); + } + } + > .join-item:where(:first-child) { + --join-ss: var(--radius-field); + --join-se: 0; + --join-es: var(--radius-field); + --join-ee: 0; + } + :first-child:not(:last-child) { + :where(.join-item) { + --join-ss: var(--radius-field); + --join-se: 0; + --join-es: var(--radius-field); + --join-ee: 0; + } + } + > .join-item:where(:last-child) { + --join-ss: 0; + --join-se: var(--radius-field); + --join-es: 0; + --join-ee: var(--radius-field); + } + :last-child:not(:first-child) { + :where(.join-item) { + --join-ss: 0; + --join-se: var(--radius-field); + --join-es: 0; + --join-ee: var(--radius-field); + } + } + > .join-item:where(:only-child) { + --join-ss: var(--radius-field); + --join-se: var(--radius-field); + --join-es: var(--radius-field); + --join-ee: var(--radius-field); + } + :only-child { + :where(.join-item) { + --join-ss: var(--radius-field); + --join-se: var(--radius-field); + --join-es: var(--radius-field); + --join-ee: var(--radius-field); + } + } + } .flex { display: flex; } .inline-flex { display: inline-flex; } + .table { + display: table; + } + .btn-square { + @layer daisyui.l1.l2 { + padding-inline: calc(0.25rem * 0); + width: var(--size); + height: var(--size); + } + } .h-4 { height: calc(var(--spacing) * 4); } + .h-5 { + height: calc(var(--spacing) * 5); + } .h-8 { height: calc(var(--spacing) * 8); } .h-10 { height: calc(var(--spacing) * 10); } + .h-14 { + height: calc(var(--spacing) * 14); + } + .h-full { + height: 100%; + } .h-px { height: 1px; } .h-screen { height: 100vh; } + .min-h-8 { + min-height: calc(var(--spacing) * 8); + } + .min-h-14 { + min-height: calc(var(--spacing) * 14); + } .w-4 { width: calc(var(--spacing) * 4); } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-8 { + width: calc(var(--spacing) * 8); + } .w-20 { width: calc(var(--spacing) * 20); } @@ -293,15 +1067,18 @@ .w-full { width: 100%; } - .w-px { - width: 1px; + .w-screen { + width: 100vw; } - .max-w-\[200px\] { - max-width: 200px; + .max-w-full { + max-width: 100%; } .max-w-sm { max-width: var(--container-sm); } + .max-w-xs { + max-width: var(--container-xs); + } .min-w-0 { min-width: calc(var(--spacing) * 0); } @@ -314,6 +1091,9 @@ .transform { transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); } + .cursor-pointer { + cursor: pointer; + } .flex-col { flex-direction: column; } @@ -329,9 +1109,6 @@ .justify-end { justify-content: flex-end; } - .justify-start { - justify-content: flex-start; - } .gap-1 { gap: calc(var(--spacing) * 1); } @@ -344,61 +1121,29 @@ .gap-4 { gap: calc(var(--spacing) * 4); } - .space-y-1 { - :where(& > :not(:last-child)) { - --tw-space-y-reverse: 0; - margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); - margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); - } - } - .space-y-4 { - :where(& > :not(:last-child)) { - --tw-space-y-reverse: 0; - margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); - margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); - } - } - .space-x-4 { - :where(& > :not(:last-child)) { - --tw-space-x-reverse: 0; - margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); - margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); - } - } - .divide-y { - :where(& > :not(:last-child)) { - --tw-divide-y-reverse: 0; - border-bottom-style: var(--tw-border-style); - border-top-style: var(--tw-border-style); - border-top-width: calc(1px * var(--tw-divide-y-reverse)); - border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); - } - } - .divide-border { - :where(& > :not(:last-child)) { - border-color: var(--color-border); - } - } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .overflow-auto { - overflow: auto; - } .overflow-hidden { overflow: hidden; } - .overflow-y-auto { - overflow-y: auto; + .overflow-x-auto { + overflow-x: auto; } - .rounded-full { - border-radius: calc(infinity * 1px); + .rounded-box { + border-radius: var(--radius-box); + } + .rounded-box { + border-radius: var(--radius-box); } .rounded-md { border-radius: var(--radius-md); } + .rounded-none { + border-radius: 0; + } .rounded-xl { border-radius: var(--radius-xl); } @@ -422,11 +1167,19 @@ border-bottom-style: var(--tw-border-style); border-bottom-width: 1px; } - .border-border { - border-color: var(--color-border); + .badge-ghost { + @layer daisyui.l1.l2 { + border-color: var(--color-base-200); + background-color: var(--color-base-200); + color: var(--color-base-content); + background-image: none; + } } - .border-input { - border-color: var(--color-input); + .border-base-200 { + border-color: var(--color-base-200); + } + .border-base-300 { + border-color: var(--color-base-300); } .border-white\/10 { border-color: color-mix(in srgb, #fff 10%, transparent); @@ -437,44 +1190,11 @@ .bg-\[\#111116\]\/95 { background-color: color-mix(in oklab, #111116 95%, transparent); } - .bg-background { - background-color: var(--color-background); + .bg-base-100 { + background-color: var(--color-base-100); } - .bg-background\/80 { - background-color: color-mix(in srgb, hsl(var(--background)) 80%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-background) 80%, transparent); - } - } - .bg-background\/95 { - background-color: color-mix(in srgb, hsl(var(--background)) 95%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-background) 95%, transparent); - } - } - .bg-border { - background-color: var(--color-border); - } - .bg-card { - background-color: var(--color-card); - } - .bg-card\/30 { - background-color: color-mix(in srgb, hsl(var(--card)) 30%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-card) 30%, transparent); - } - } - .bg-destructive { - background-color: var(--color-destructive); - } - .bg-input { - background-color: var(--color-input); - } - .bg-muted\/50 { - background-color: color-mix(in srgb, hsl(var(--muted)) 50%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-muted) 50%, transparent); - } + .bg-base-200 { + background-color: var(--color-base-200); } .bg-primary { background-color: var(--color-primary); @@ -485,8 +1205,11 @@ background-color: color-mix(in oklab, var(--color-white) 10%, transparent); } } - .p-2 { - padding: calc(var(--spacing) * 2); + .checkbox-xs { + @layer daisyui.l1.l2 { + padding: 0.125rem; + --size: calc(var(--size-selector, 0.25rem) * 4); + } } .p-4 { padding: calc(var(--spacing) * 4); @@ -494,11 +1217,23 @@ .p-6 { padding: calc(var(--spacing) * 6); } - .px-1\.5 { - padding-inline: calc(var(--spacing) * 1.5); + .table-xs { + @layer daisyui.l1.l2 { + :not(thead, tfoot) tr { + font-size: 0.6875rem; + } + :where(th, td) { + padding-inline: calc(0.25rem * 2); + padding-block: calc(0.25rem * 1); + } + } } - .px-2 { - padding-inline: calc(var(--spacing) * 2); + .badge-sm { + @layer daisyui.l1.l2 { + --size: calc(var(--size-selector, 0.25rem) * 5); + font-size: 0.75rem; + padding-inline: calc(0.25rem * 2.5 - var(--border)); + } } .px-3 { padding-inline: calc(var(--spacing) * 3); @@ -506,36 +1241,23 @@ .px-4 { padding-inline: calc(var(--spacing) * 4); } - .py-0\.5 { - padding-block: calc(var(--spacing) * 0.5); - } .py-1 { padding-block: calc(var(--spacing) * 1); } - .py-1\.5 { - padding-block: calc(var(--spacing) * 1.5); - } .py-2 { padding-block: calc(var(--spacing) * 2); } .py-2\.5 { padding-block: calc(var(--spacing) * 2.5); } - .text-center { - text-align: center; - } .text-left { text-align: left; } .text-right { text-align: right; } - .font-sans { - font-family: var(--font-sans); - } - .text-2xl { - font-size: var(--text-2xl); - line-height: var(--tw-leading, var(--text-2xl--line-height)); + .font-mono { + font-family: var(--font-mono); } .text-lg { font-size: var(--text-lg); @@ -545,6 +1267,10 @@ font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } .text-xs { font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); @@ -552,6 +1278,9 @@ .text-\[10px\] { font-size: 10px; } + .text-\[11px\] { + font-size: 11px; + } .font-bold { --tw-font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold); @@ -568,20 +1297,33 @@ --tw-tracking: var(--tracking-wider); letter-spacing: var(--tracking-wider); } - .whitespace-nowrap { - white-space: nowrap; + .progress-primary { + @layer daisyui.l1.l2 { + color: var(--color-primary); + } } - .text-blue-500 { - color: var(--color-blue-500); + .progress-success { + @layer daisyui.l1.l2 { + color: var(--color-success); + } } - .text-card-foreground { - color: var(--color-card-foreground); + .text-base-content { + color: var(--color-base-content); } - .text-destructive-foreground { - color: var(--color-destructive-foreground); + .text-base-content\/50 { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 50%, transparent); + } } - .text-foreground { - color: var(--color-foreground); + .text-base-content\/70 { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 70%, transparent); + } + } + .text-error { + color: var(--color-error); } .text-gray-500 { color: var(--color-gray-500); @@ -589,11 +1331,8 @@ .text-green-500 { color: var(--color-green-500); } - .text-muted-foreground { - color: var(--color-muted-foreground); - } - .text-primary-foreground { - color: var(--color-primary-foreground); + .text-primary { + color: var(--color-primary); } .text-red-500 { color: var(--color-red-500); @@ -601,6 +1340,12 @@ .text-red-600 { color: var(--color-red-600); } + .text-success { + color: var(--color-success); + } + .text-warning { + color: var(--color-warning); + } .text-white { color: var(--color-white); } @@ -610,6 +1355,12 @@ .uppercase { text-transform: uppercase; } + .opacity-70 { + opacity: 70%; + } + .opacity-80 { + opacity: 80%; + } .shadow-2xl { --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); @@ -622,13 +1373,29 @@ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } - .ring-offset-background { - --tw-ring-offset-color: var(--color-background); - } - .backdrop-blur { - --tw-backdrop-blur: blur(8px); - -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + .btn-ghost { + @layer daisyui.l1 { + &:not(.btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn)) { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-border: #0000; + --btn-noise: none; + &:not(:disabled, [disabled], .btn-disabled) { + outline-color: currentcolor; + --btn-fg: var(--btn-color, currentColor); + } + } + @media (hover: none) { + &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { + outline-color: currentcolor; + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color, currentColor); + --btn-border: #0000; + --btn-noise: none; + } + } + } } .backdrop-blur-sm { --tw-backdrop-blur: blur(var(--blur-sm)); @@ -658,6 +1425,44 @@ --tw-duration: 200ms; transition-duration: 200ms; } + .btn-outline { + @layer daisyui.l1 { + &:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color); + --btn-border: var(--btn-color); + --btn-noise: none; + } + @media (hover: none) { + &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color); + --btn-border: var(--btn-color); + --btn-noise: none; + } + } + } + } + .btn-sm { + @layer daisyui.l1.l2 { + --fontsize: 0.75rem; + --btn-p: 0.75rem; + --size: calc(var(--size-field, 0.25rem) * 8); + } + } + .btn-xs { + @layer daisyui.l1.l2 { + --fontsize: 0.6875rem; + --btn-p: 0.5rem; + --size: calc(var(--size-field, 0.25rem) * 6); + } + } + .select-none { + -webkit-user-select: none; + user-select: none; + } .hover\:bg-accent { &:hover { @media (hover: hover) { @@ -665,30 +1470,10 @@ } } } - .hover\:bg-destructive\/90 { - &:hover { - @media (hover: hover) { - background-color: color-mix(in srgb, hsl(var(--destructive)) 90%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-destructive) 90%, transparent); - } - } - } - } - .hover\:bg-muted\/50 { - &:hover { - @media (hover: hover) { - background-color: color-mix(in srgb, hsl(var(--muted)) 50%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-muted) 50%, transparent); - } - } - } - } .hover\:bg-primary\/90 { &:hover { @media (hover: hover) { - background-color: color-mix(in srgb, hsl(var(--primary)) 90%, transparent); + background-color: var(--color-primary); @supports (color: color-mix(in lab, red, red)) { background-color: color-mix(in oklab, var(--color-primary) 90%, transparent); } @@ -725,10 +1510,10 @@ } } } - .hover\:text-accent-foreground { + .hover\:text-primary { &:hover { @media (hover: hover) { - color: var(--color-accent-foreground); + color: var(--color-primary); } } } @@ -739,34 +1524,12 @@ } } } - .focus\:ring-1 { - &:focus { - --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); - } - } - .focus\:ring-ring { - &:focus { - --tw-ring-color: var(--color-ring); - } - } - .focus\:outline-none { - &:focus { - --tw-outline-style: none; - outline-style: none; - } - } .focus-visible\:ring-2 { &:focus-visible { --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } } - .focus-visible\:ring-ring { - &:focus-visible { - --tw-ring-color: var(--color-ring); - } - } .focus-visible\:ring-offset-2 { &:focus-visible { --tw-ring-offset-width: 2px; @@ -789,14 +1552,6 @@ opacity: 50%; } } - .supports-\[backdrop-filter\]\:bg-background\/60 { - @supports (backdrop-filter: var(--tw)) { - background-color: color-mix(in srgb, hsl(var(--background)) 60%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-background) 60%, transparent); - } - } - } .sm\:p-4 { @media (width >= 40rem) { padding: calc(var(--spacing) * 4); @@ -814,77 +1569,255 @@ } } @layer base { - html, body, button, a, [role="button"], input, label, select, summary, textarea { - touch-action: manipulation; + html, body { + height: 100%; + width: 100%; + overflow: hidden; + background-color: var(--color-base-100); + color: var(--color-base-content); + } +} +@layer base { + :where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] { + color-scheme: light; + --color-base-100: oklch(100% 0 0); + --color-base-200: oklch(98% 0 0); + --color-base-300: oklch(95% 0 0); + --color-base-content: oklch(21% 0.006 285.885); + --color-primary: oklch(45% 0.24 277.023); + --color-primary-content: oklch(93% 0.034 272.788); + --color-secondary: oklch(65% 0.241 354.308); + --color-secondary-content: oklch(94% 0.028 342.258); + --color-accent: oklch(77% 0.152 181.912); + --color-accent-content: oklch(38% 0.063 188.416); + --color-neutral: oklch(14% 0.005 285.823); + --color-neutral-content: oklch(92% 0.004 286.32); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.25rem; + --size-field: 0.25rem; + --border: 1px; + --depth: 1; + --noise: 0; + } +} +@layer base { + @media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + color-scheme: dark; + --color-base-100: oklch(25.33% 0.016 252.42); + --color-base-200: oklch(23.26% 0.014 253.1); + --color-base-300: oklch(21.15% 0.012 254.09); + --color-base-content: oklch(97.807% 0.029 256.847); + --color-primary: oklch(58% 0.233 277.117); + --color-primary-content: oklch(96% 0.018 272.314); + --color-secondary: oklch(65% 0.241 354.308); + --color-secondary-content: oklch(94% 0.028 342.258); + --color-accent: oklch(77% 0.152 181.912); + --color-accent-content: oklch(38% 0.063 188.416); + --color-neutral: oklch(14% 0.005 285.823); + --color-neutral-content: oklch(92% 0.004 286.32); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.25rem; + --size-field: 0.25rem; + --border: 1px; + --depth: 1; + --noise: 0; + } + } +} +@layer base { + :root:has(input.theme-controller[value=light]:checked),[data-theme=light] { + color-scheme: light; + --color-base-100: oklch(100% 0 0); + --color-base-200: oklch(98% 0 0); + --color-base-300: oklch(95% 0 0); + --color-base-content: oklch(21% 0.006 285.885); + --color-primary: oklch(45% 0.24 277.023); + --color-primary-content: oklch(93% 0.034 272.788); + --color-secondary: oklch(65% 0.241 354.308); + --color-secondary-content: oklch(94% 0.028 342.258); + --color-accent: oklch(77% 0.152 181.912); + --color-accent-content: oklch(38% 0.063 188.416); + --color-neutral: oklch(14% 0.005 285.823); + --color-neutral-content: oklch(92% 0.004 286.32); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.25rem; + --size-field: 0.25rem; + --border: 1px; + --depth: 1; + --noise: 0; + } +} +@layer base { + :root:has(input.theme-controller[value=dark]:checked),[data-theme=dark] { + color-scheme: dark; + --color-base-100: oklch(25.33% 0.016 252.42); + --color-base-200: oklch(23.26% 0.014 253.1); + --color-base-300: oklch(21.15% 0.012 254.09); + --color-base-content: oklch(97.807% 0.029 256.847); + --color-primary: oklch(58% 0.233 277.117); + --color-primary-content: oklch(96% 0.018 272.314); + --color-secondary: oklch(65% 0.241 354.308); + --color-secondary-content: oklch(94% 0.028 342.258); + --color-accent: oklch(77% 0.152 181.912); + --color-accent-content: oklch(38% 0.063 188.416); + --color-neutral: oklch(14% 0.005 285.823); + --color-neutral-content: oklch(92% 0.004 286.32); + --color-info: oklch(74% 0.16 232.661); + --color-info-content: oklch(29% 0.066 243.157); + --color-success: oklch(76% 0.177 163.223); + --color-success-content: oklch(37% 0.077 168.94); + --color-warning: oklch(82% 0.189 84.429); + --color-warning-content: oklch(41% 0.112 45.904); + --color-error: oklch(71% 0.194 13.428); + --color-error-content: oklch(27% 0.105 12.094); + --radius-selector: 0.5rem; + --radius-field: 0.25rem; + --radius-box: 0.5rem; + --size-selector: 0.25rem; + --size-field: 0.25rem; + --border: 1px; + --depth: 1; + --noise: 0; } } @layer base { :root { - --background: 0 0% 100%; - --foreground: 240 10% 3.9%; - --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; - --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 240 5.9% 10%; - --primary-foreground: 0 0% 98%; - --secondary: 240 4.8% 95.9%; - --secondary-foreground: 240 5.9% 10%; - --muted: 240 4.8% 95.9%; - --muted-foreground: 240 3.8% 46.1%; - --accent: 240 4.8% 95.9%; - --accent-foreground: 240 5.9% 10%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 0 0% 98%; - --border: 240 5.9% 90%; - --input: 240 5.9% 90%; - --ring: 240 10% 3.9%; - --radius: 0.5rem; + --fx-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E"); } - .dark { - --background: 240 10% 3.9%; - --foreground: 0 0% 98%; - --card: 240 10% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 240 10% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 240 5.9% 10%; - --secondary: 240 3.7% 15.9%; - --secondary-foreground: 0 0% 98%; - --muted: 240 3.7% 15.9%; - --muted-foreground: 240 5% 64.9%; - --accent: 240 3.7% 15.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 240 3.7% 15.9%; - --input: 240 3.7% 15.9%; - --ring: 240 4.9% 83.9%; +} +@layer base { + :root, [data-theme] { + background-color: var(--root-bg, var(--color-base-100)); + color: var(--color-base-content); } - .amoled { - --background: 0 0% 0%; - --foreground: 0 0% 98%; - --card: 0 0% 0%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 0%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 0%; - --secondary: 0 0% 9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 9%; - --muted-foreground: 0 0% 60%; - --accent: 0 0% 9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 12%; - --input: 0 0% 12%; - --ring: 0 0% 83.9%; + :where(:root, [data-theme]) { + --root-bg: var(--color-base-100); } - * { - border-color: var(--color-border); +} +@layer base { + :root { + --page-scroll-bg-on: linear-gradient(var(--root-bg), var(--root-bg)) + var(--root-bg); + @supports (color: color-mix(in lab, red, red)) { + --page-scroll-bg-on: linear-gradient(var(--root-bg), var(--root-bg)) + color-mix(in srgb, var(--root-bg), oklch(0% 0 0) calc(var(--page-has-backdrop, 0) * 40%)); + } + --page-scroll-transition-on: background-color 0.3s ease-out; + transition: var(--page-scroll-transition); + scrollbar-gutter: var(--page-scroll-gutter, unset); + scrollbar-gutter: if(style(--page-has-scroll: 1): var(--page-scroll-gutter, unset) ; else: unset); + } + :root:root { + background: var(--page-scroll-bg, var(--root-bg, var(--color-base-100))); + } + @keyframes set-page-has-scroll { + 0%, to { + --page-has-scroll: 1; + } + } +} +@layer base { + @property --radialprogress { + syntax: ""; + inherits: true; + initial-value: 0%; + } +} +@layer base { + :root:not(span) { + overflow: var(--page-overflow); + } +} +@layer base { + :root { + scrollbar-color: currentColor #0000; + @supports (color: color-mix(in lab, red, red)) { + scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000; + } + } +} +@keyframes dropdown { + 0% { + opacity: 0; + } +} +@keyframes rating { + 0%, 40% { + scale: 1.1; + filter: brightness(1.05) contrast(1.05); + } +} +@keyframes radio { + 0% { + padding: 5px; + } + 50% { + padding: 3px; + } +} +@keyframes progress { + 50% { + background-position-x: -115%; + } +} +@keyframes skeleton { + 0% { + background-position: 150%; + } + 100% { + background-position: -50%; + } +} +@keyframes toast { + 0% { + scale: 0.9; + opacity: 0; + } + 100% { + scale: 1; + opacity: 1; + } +} +@keyframes rotator { + 89.9999%, 100% { + --first-item-position: 0 0%; + } + 90%, 99.9999% { + --first-item-position: 0 calc(var(--items) * 100%); + } + 100% { + translate: 0 -100%; } } @property --tw-rotate-x { @@ -907,21 +1840,6 @@ syntax: "*"; inherits: false; } -@property --tw-space-y-reverse { - syntax: "*"; - inherits: false; - initial-value: 0; -} -@property --tw-space-x-reverse { - syntax: "*"; - inherits: false; - initial-value: 0; -} -@property --tw-divide-y-reverse { - syntax: "*"; - inherits: false; - initial-value: 0; -} @property --tw-border-style { syntax: "*"; inherits: false; @@ -1048,9 +1966,6 @@ --tw-rotate-z: initial; --tw-skew-x: initial; --tw-skew-y: initial; - --tw-space-y-reverse: 0; - --tw-space-x-reverse: 0; - --tw-divide-y-reverse: 0; --tw-border-style: solid; --tw-font-weight: initial; --tw-tracking: initial; diff --git a/frontend/src/app.rs b/frontend/src/app.rs index 1840f18..b3b1cc1 100644 --- a/frontend/src/app.rs +++ b/frontend/src/app.rs @@ -1,239 +1,28 @@ use leptos::*; -use thaw::*; -use shared::{Torrent, AppEvent, TorrentStatus, Theme}; -use crate::components::toolbar::Toolbar; -use crate::components::sidebar::Sidebar; -// use crate::components::context_menu::ContextMenu; -// use crate::components::modal::Modal; -use crate::components::status_bar::StatusBar; -use crate::components::torrent_table::TorrentTable; -use gloo_net::eventsource::futures::EventSource; -use futures::StreamExt; +use crate::components::layout::sidebar::Sidebar; +use crate::components::layout::toolbar::Toolbar; +use crate::components::layout::statusbar::StatusBar; +use crate::components::torrent::table::TorrentTable; #[component] pub fn App() -> impl IntoView { - // Signals - let (torrents, set_torrents) = create_signal(Vec::::new()); - let (sort_key, set_sort_key) = create_signal(6); // 6=Added Date - let (sort_asc, set_sort_asc) = create_signal(false); // Descending (Newest first) - let (filter_status, set_filter_status) = create_signal(Option::::None); - let (active_tab, set_active_tab) = create_signal("torrents"); - - // Theme with Persistence - let (theme, set_theme) = create_signal({ - let storage = window().local_storage().ok().flatten(); - let saved = storage.and_then(|s| s.get_item("vibetorrent_theme").ok().flatten()); - match saved.as_deref() { - Some("Light") => Theme::Light, - Some("Amoled") => Theme::Amoled, - _ => Theme::Midnight, - } - }); - - // Persist Theme Logic - create_effect(move |_| { - let val = match theme.get() { - Theme::Midnight => "Midnight", - Theme::Light => "Light", - Theme::Amoled => "Amoled", - }; - - if let Some(doc) = window().document() { - if let Some(body) = doc.body() { - let list = body.class_list(); - // Reset classes - let _ = list.remove_1("dark"); - let _ = list.remove_1("amoled"); - - match theme.get() { - Theme::Light => {}, - Theme::Midnight => { let _ = list.add_1("dark"); }, - Theme::Amoled => { - let _ = list.add_1("dark"); - let _ = list.add_1("amoled"); - }, - } - } - } - - if let Some(storage) = window().local_storage().ok().flatten() { - let _ = storage.set_item("vibetorrent_theme", val); - } - }); - - // Remove Loading Spinner - create_effect(move |_| { - if let Some(doc) = window().document() { - if let Some(el) = doc.get_element_by_id("app-loading") { - el.remove(); - } - } - }); - - // Debug: Last Updated Timestamp - let (last_updated, set_last_updated) = create_signal(0u64); - - // Derived: Filtered & Sorted Logic - let processed_torrents = create_memo(move |_| { - let mut items = torrents.get(); - if let Some(status) = filter_status.get() { - items.retain(|t| t.status == status); - } - - let key = sort_key.get(); - let asc = sort_asc.get(); - - items.sort_by(|a, b| { - let cmp = match key { - 0 => a.name.to_lowercase().cmp(&b.name.to_lowercase()), - 1 => a.size.cmp(&b.size), - 2 => a.percent_complete.partial_cmp(&b.percent_complete).unwrap_or(std::cmp::Ordering::Equal), - 3 => a.down_rate.cmp(&b.down_rate), - 4 => a.up_rate.cmp(&b.up_rate), - 5 => a.eta.cmp(&b.eta), - 6 => a.added_date.cmp(&b.added_date), - _ => std::cmp::Ordering::Equal, - }; - if asc { cmp } else { cmp.reverse() } - }); - items - }); - - // Add Torrent Logic - let (show_modal, set_show_modal) = create_signal(false); - let (magnet_link, set_magnet_link) = create_signal(String::new()); - - let add_torrent = move |_| { - spawn_local(async move { - let uri = magnet_link.get(); - if uri.is_empty() { return; } - let client = gloo_net::http::Request::post("/api/torrents/add") - .header("Content-Type", "application/json") - .body(serde_json::to_string(&serde_json::json!({ "uri": uri })).unwrap()) - .unwrap(); - if client.send().await.is_ok() { - set_magnet_link.set(String::new()); - set_show_modal.set(false); - } - }); - }; - - // Connect SSE - create_effect(move |_| { - spawn_local(async move { - let mut es = EventSource::new("/api/events").unwrap(); - let mut stream = es.subscribe("message").unwrap(); - - loop { - match stream.next().await { - Some(Ok((_, msg))) => { - let data = msg.data().as_string().unwrap(); - if let Ok(event) = serde_json::from_str::(&data) { - match event { - AppEvent::FullList(list, ts) => { - set_torrents.set(list); - set_last_updated.set(ts); - } - AppEvent::Update(diff) => { - set_torrents.update(|list| { - if let Some(target) = list.iter_mut().find(|t| t.hash == diff.hash) { - if let Some(v) = diff.name { target.name = v; } - if let Some(v) = diff.size { target.size = v; } - if let Some(v) = diff.down_rate { target.down_rate = v; } - if let Some(v) = diff.up_rate { target.up_rate = v; } - if let Some(v) = diff.percent_complete { target.percent_complete = v; } - if let Some(v) = diff.completed { target.completed = v; } - if let Some(v) = diff.eta { target.eta = v; } - if let Some(v) = diff.status { target.status = v; } - if let Some(v) = diff.error_message { target.error_message = v; } - } - }); - } - } - } - } - None => break, - _ => {} - } - } - }); - }); - - // Toolbar Callbacks - let on_add = Callback::new(move |_| set_show_modal.set(true)); - let on_start = Callback::new(move |_| logging::log!("Start all - to be implemented with selection")); - let on_pause = Callback::new(move |_| logging::log!("Pause all - to be implemented with selection")); - let on_delete = Callback::new(move |_| logging::log!("Delete - to be implemented with selection")); - let on_settings = Callback::new(move |_| set_active_tab.set(if active_tab.get() == "settings" { "torrents" } else { "settings" })); - view! { -
- -
- - - {move || if active_tab.get() == "settings" { - view! { -
-

"Settings"

-
- - - -
-
- }.into_view() - } else { - view! { }.into_view() - }} +
+ // Toolbar at the top + - +
+ // Sidebar on the left + + + // Main Content Area +
+ +
- // Add Torrent Modal (Inlined) - -
-
-

"Add Torrent"

- -
-

"Paste a magnet link or URL to start downloading."

- -
- -
- - -
-
-
-
+ // Status Bar at the bottom +
} } diff --git a/frontend/src/components/layout/mod.rs b/frontend/src/components/layout/mod.rs new file mode 100644 index 0000000..dab70fb --- /dev/null +++ b/frontend/src/components/layout/mod.rs @@ -0,0 +1,3 @@ +pub mod sidebar; +pub mod toolbar; +pub mod statusbar; diff --git a/frontend/src/components/layout/sidebar.rs b/frontend/src/components/layout/sidebar.rs new file mode 100644 index 0000000..eb95f58 --- /dev/null +++ b/frontend/src/components/layout/sidebar.rs @@ -0,0 +1,65 @@ +use leptos::*; + +#[component] +pub fn Sidebar() -> impl IntoView { + view! { + + } +} diff --git a/frontend/src/components/layout/statusbar.rs b/frontend/src/components/layout/statusbar.rs new file mode 100644 index 0000000..c4ee9a5 --- /dev/null +++ b/frontend/src/components/layout/statusbar.rs @@ -0,0 +1,35 @@ +use leptos::*; + +#[component] +pub fn StatusBar() -> impl IntoView { + view! { +
+
+ + + + "0 KB/s" +
+
+ + + + "0 KB/s" +
+ +
+ + +
+
+ } +} diff --git a/frontend/src/components/layout/toolbar.rs b/frontend/src/components/layout/toolbar.rs new file mode 100644 index 0000000..ed015a4 --- /dev/null +++ b/frontend/src/components/layout/toolbar.rs @@ -0,0 +1,48 @@ +use leptos::*; + +#[component] +pub fn Toolbar() -> impl IntoView { + view! { +
+
+ + +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+ } +} diff --git a/frontend/src/components/mod.rs b/frontend/src/components/mod.rs index e00000a..a98eab0 100644 --- a/frontend/src/components/mod.rs +++ b/frontend/src/components/mod.rs @@ -1,7 +1,4 @@ pub mod modal; pub mod context_menu; -pub mod toolbar; -pub mod sidebar; -pub mod status_bar; -pub mod torrent_table; - +pub mod layout; +pub mod torrent; diff --git a/frontend/src/components/modal.rs b/frontend/src/components/modal.rs index 06dff21..05f5746 100644 --- a/frontend/src/components/modal.rs +++ b/frontend/src/components/modal.rs @@ -6,7 +6,7 @@ pub fn Modal( children: Children, #[prop(into)] on_confirm: Callback<()>, #[prop(into)] on_cancel: Callback<()>, - #[prop(into)] is_open: MaybeSignal, + #[prop(into)] visible: Signal, #[prop(into, default = "Confirm".to_string())] confirm_text: String, #[prop(into, default = "Cancel".to_string())] cancel_text: String, #[prop(into, default = false)] is_danger: bool, @@ -20,7 +20,7 @@ pub fn Modal( let cancel_text = store_value(cancel_text); view! { - +

{title.get_value()}

diff --git a/frontend/src/components/sidebar.rs b/frontend/src/components/sidebar.rs deleted file mode 100644 index 117405d..0000000 --- a/frontend/src/components/sidebar.rs +++ /dev/null @@ -1,52 +0,0 @@ -use leptos::*; -use thaw::*; -use shared::TorrentStatus; - -#[component] -pub fn Sidebar( - #[prop(into)] active_filter: Signal>, - #[prop(into)] on_filter_change: Callback>, -) -> impl IntoView { - view! { -
-
"Groups"
-
- - - - - -
-
- } -} diff --git a/frontend/src/components/status_bar.rs b/frontend/src/components/status_bar.rs deleted file mode 100644 index 6025edc..0000000 --- a/frontend/src/components/status_bar.rs +++ /dev/null @@ -1,20 +0,0 @@ -use leptos::*; -use thaw::*; - -#[component] -pub fn StatusBar() -> impl IntoView { - view! { -
-
- - "0 KB/s" -
-
- - "0 KB/s" -
-
-
"Free Space: 700 GB"
-
- } -} diff --git a/frontend/src/components/toolbar.rs b/frontend/src/components/toolbar.rs deleted file mode 100644 index 96f76b4..0000000 --- a/frontend/src/components/toolbar.rs +++ /dev/null @@ -1,34 +0,0 @@ -use leptos::*; -use thaw::*; - -#[component] -pub fn Toolbar( - #[prop(into)] on_add: Callback<()>, - #[prop(into)] on_start: Callback<()>, - #[prop(into)] on_pause: Callback<()>, - #[prop(into)] on_delete: Callback<()>, - #[prop(into)] on_settings: Callback<()>, -) -> impl IntoView { - view! { -
- -
- - - -
- - -
- } -} diff --git a/frontend/src/components/torrent/mod.rs b/frontend/src/components/torrent/mod.rs new file mode 100644 index 0000000..13971b0 --- /dev/null +++ b/frontend/src/components/torrent/mod.rs @@ -0,0 +1 @@ +pub mod table; diff --git a/frontend/src/components/torrent/table.rs b/frontend/src/components/torrent/table.rs new file mode 100644 index 0000000..5405e06 --- /dev/null +++ b/frontend/src/components/torrent/table.rs @@ -0,0 +1,124 @@ +use leptos::*; + +#[derive(Clone)] +struct Torrent { + id: u32, + name: String, + size: String, + progress: f32, + status: String, + seeds: u32, + peers: u32, + down_speed: String, + up_speed: String, +} + +#[component] +pub fn TorrentTable() -> impl IntoView { + let torrents = vec![ + Torrent { + id: 1, + name: "Ubuntu 22.04.3 LTS".to_string(), + size: "4.7 GB".to_string(), + progress: 100.0, + status: "Seeding".to_string(), + seeds: 452, + peers: 12, + down_speed: "0 KB/s".to_string(), + up_speed: "1.2 MB/s".to_string(), + }, + Torrent { + id: 2, + name: "Debian 12.1.0 DVD".to_string(), + size: "3.9 GB".to_string(), + progress: 45.5, + status: "Downloading".to_string(), + seeds: 120, + peers: 45, + down_speed: "4.5 MB/s".to_string(), + up_speed: "50 KB/s".to_string(), + }, + Torrent { + id: 3, + name: "Arch Linux 2023.09.01".to_string(), + size: "800 MB".to_string(), + progress: 12.0, + status: "Downloading".to_string(), + seeds: 85, + peers: 20, + down_speed: "2.1 MB/s".to_string(), + up_speed: "10 KB/s".to_string(), + }, + Torrent { + id: 4, + name: "Fedora Workstation 39".to_string(), + size: "2.1 GB".to_string(), + progress: 0.0, + status: "Paused".to_string(), + seeds: 0, + peers: 0, + down_speed: "0 KB/s".to_string(), + up_speed: "0 KB/s".to_string(), + }, + ]; + + view! { +
+ + + + + + + + + + + + + + + + {torrents.into_iter().map(|t| { + let progress_class = if t.progress == 100.0 { "progress-success" } else { "progress-primary" }; + let status_class = match t.status.as_str() { + "Seeding" => "text-success", + "Downloading" => "text-primary", + "Paused" => "text-warning", + _ => "text-base-content/50" + }; + + view! { + + + + + + + + + + + + } + }).collect::>()} + +
+ + "Name""Size""Progress""Status""Seeds""Peers""Down Speed""Up Speed"
+ + + {t.name} + {t.size} +
+ + {format!("{:.1}%", t.progress)} +
+
{t.status}{t.seeds}{t.peers}{t.down_speed}{t.up_speed}
+
+ } +} diff --git a/frontend/src/components/torrent_table.rs b/frontend/src/components/torrent_table.rs deleted file mode 100644 index a7d65f1..0000000 --- a/frontend/src/components/torrent_table.rs +++ /dev/null @@ -1,51 +0,0 @@ -use leptos::*; -use thaw::*; -use shared::Torrent; - -#[component] -pub fn TorrentTable( - #[prop(into)] torrents: Signal> -) -> impl IntoView { - view! { -
- - - - - - - - - - - - - - - - - - - - - - - } - } - /> - -
"Name""Size""Progress""Status""Down""Up""ETA"
{torrent.name}{torrent.size} - - - - {format!("{:?}", torrent.status)} - - {torrent.down_rate}{torrent.up_rate}{torrent.eta}
-
- } -} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 68b4419..b60602f 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -13,4 +13,5 @@ module.exports = { }, }, plugins: [], + }