From c848a2758df7c253629227ca0462f7f6566d15f0 Mon Sep 17 00:00:00 2001 From: Adam Doyle <24621027+adoyle0@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:54:15 -0500 Subject: [PATCH 001/120] refactor: rework popover components to use css anchor positioning and add external/manual popover control, add submenus, add `CheckboxItem` and `RadioItem`, add `PopoverHeader` components (#45) * refactor: rework popovers * add reactive `dismiss` prop to popovers and make `dismissable` reactive * update docs and add inset prop to `DropdownMenuLabel` and `DropdownMenuItem` * css * more css * add submenus for dropdown and context menus * add more examples * fix submenu positioning * add `CheckboxItem` and `RadioItem` * tweaks and docs * open submenus on hover * run clippy * finish `Popover` stuff, add `PopoverHeader`, `PopoverTitle`, `PopoverDescription`, docs, and examples * custom class support for popover header/children --- Cargo.lock | 170 ++++++------- Cargo.toml | 2 +- .../breadcrumb/examples/breadcrumb.rs | 5 +- .../button_group/examples/button_group.rs | 2 +- .../examples/button_group_dropdown.rs | 6 +- .../examples/button_group_popover.rs | 30 +-- .../routes/components/context_menu/anatomy.rs | 19 +- .../components/context_menu/component.toml | 140 ++++++++++- .../context_menu/examples/context_basic.rs | 21 ++ .../examples/context_checkboxes.rs | 21 ++ .../examples/context_destructive.rs | 26 ++ .../context_menu/examples/context_groups.rs | 56 +++++ .../context_menu/examples/context_icons.rs | 27 +++ .../context_menu/examples/context_menu.rs | 54 +++-- .../context_menu/examples/context_radio.rs | 35 +++ .../examples/context_shortcuts.rs | 36 +++ .../context_menu/examples/context_sub.rs | 42 ++++ .../components/context_menu/examples/mod.rs | 16 ++ .../src/routes/components/dropdown/anatomy.rs | 17 ++ .../routes/components/dropdown/component.toml | 170 ++++++++++++- .../dropdown/examples/dropdown_avatar.rs | 27 +++ .../dropdown/examples/dropdown_basic.rs | 25 ++ .../examples/dropdown_checkbox_icons.rs | 36 +++ .../dropdown/examples/dropdown_checkboxes.rs | 27 +++ .../dropdown/examples/dropdown_complex.rs | 176 ++++++++++++++ .../dropdown/examples/dropdown_destructive.rs | 25 ++ .../dropdown/examples/dropdown_icons.rs | 22 ++ .../dropdown/examples/dropdown_menu.rs | 25 +- .../dropdown/examples/dropdown_radio_group.rs | 25 ++ .../dropdown/examples/dropdown_radio_icons.rs | 28 +++ .../dropdown/examples/dropdown_shortcuts.rs | 31 +++ .../dropdown/examples/dropdown_sub.rs | 39 +++ .../components/dropdown/examples/mod.rs | 22 ++ .../field/examples/field_responsive_layout.rs | 2 +- .../examples/input_group_dropdown.rs | 4 +- docs/src/routes/components/popover/anatomy.rs | 7 +- .../routes/components/popover/component.toml | 70 +++++- .../routes/components/popover/examples/mod.rs | 7 + .../components/popover/examples/popover.rs | 20 +- .../popover/examples/popover_align.rs | 40 +++ .../popover/examples/popover_basic.rs | 19 ++ .../popover/examples/popover_form.rs | 33 +++ docs/src/routes/debug/dropdown.rs | 144 ++++++++++- singlestage/Cargo.toml | 6 +- singlestage/build.rs | 1 - .../src/components/accordion/trigger.rs | 4 +- singlestage/src/components/button/mod.rs | 27 ++- .../src/components/checkbox/checkbox.css | 6 +- .../src/components/checkbox/checkbox.rs | 2 +- .../src/components/context_menu/content.rs | 25 +- .../components/context_menu/context_menu.css | 116 --------- .../src/components/context_menu/group.rs | 4 +- .../src/components/context_menu/item.rs | 37 ++- .../src/components/context_menu/label.rs | 14 +- .../src/components/context_menu/menu.rs | 13 +- .../src/components/context_menu/mod.rs | 7 +- .../src/components/context_menu/separator.rs | 2 +- .../src/components/context_menu/shortcut.rs | 2 +- .../components/context_menu/sub/content.rs | 173 +++++++++++++ .../src/components/context_menu/sub/mod.rs | 192 +++++++++++++++ .../components/context_menu/sub/trigger.rs | 228 ++++++++++++++++++ .../src/components/context_menu/trigger.rs | 19 +- .../src/components/dropdown/checkbox.rs | 216 +++++++++++++++++ .../src/components/dropdown/content.rs | 40 ++- .../src/components/dropdown/dropdown.css | 140 ++++++----- singlestage/src/components/dropdown/group.rs | 2 +- singlestage/src/components/dropdown/item.rs | 43 +++- singlestage/src/components/dropdown/label.rs | 14 +- singlestage/src/components/dropdown/menu.rs | 13 +- singlestage/src/components/dropdown/mod.rs | 9 + singlestage/src/components/dropdown/radio.rs | 216 +++++++++++++++++ .../src/components/dropdown/sub/content.rs | 177 ++++++++++++++ .../src/components/dropdown/sub/mod.rs | 192 +++++++++++++++ .../src/components/dropdown/sub/trigger.rs | 227 +++++++++++++++++ singlestage/src/components/popover/content.rs | 42 +++- .../src/components/popover/description.rs | 165 +++++++++++++ singlestage/src/components/popover/header.rs | 163 +++++++++++++ singlestage/src/components/popover/mod.rs | 9 + .../src/components/popover/popover.css | 179 ++++++++++++-- singlestage/src/components/popover/popover.rs | 13 +- singlestage/src/components/popover/title.rs | 165 +++++++++++++ singlestage/src/components/radio/radio.css | 4 +- singlestage/src/components/radio/radio.rs | 4 +- .../src/components/theme_provider/mod.rs | 36 ++- 84 files changed, 4231 insertions(+), 465 deletions(-) create mode 100644 docs/src/routes/components/context_menu/examples/context_basic.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_checkboxes.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_destructive.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_groups.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_icons.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_radio.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_shortcuts.rs create mode 100644 docs/src/routes/components/context_menu/examples/context_sub.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_avatar.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_basic.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_checkbox_icons.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_checkboxes.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_complex.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_destructive.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_icons.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_radio_group.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_radio_icons.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_shortcuts.rs create mode 100644 docs/src/routes/components/dropdown/examples/dropdown_sub.rs create mode 100644 docs/src/routes/components/popover/examples/popover_align.rs create mode 100644 docs/src/routes/components/popover/examples/popover_basic.rs create mode 100644 docs/src/routes/components/popover/examples/popover_form.rs delete mode 100644 singlestage/src/components/context_menu/context_menu.css create mode 100644 singlestage/src/components/context_menu/sub/content.rs create mode 100644 singlestage/src/components/context_menu/sub/mod.rs create mode 100644 singlestage/src/components/context_menu/sub/trigger.rs create mode 100644 singlestage/src/components/dropdown/checkbox.rs create mode 100644 singlestage/src/components/dropdown/radio.rs create mode 100644 singlestage/src/components/dropdown/sub/content.rs create mode 100644 singlestage/src/components/dropdown/sub/mod.rs create mode 100644 singlestage/src/components/dropdown/sub/trigger.rs create mode 100644 singlestage/src/components/popover/description.rs create mode 100644 singlestage/src/components/popover/header.rs create mode 100644 singlestage/src/components/popover/title.rs diff --git a/Cargo.lock b/Cargo.lock index dcebf221..0c65e5af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,7 +39,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" dependencies = [ "futures", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "wasm-bindgen-futures", ] @@ -52,13 +52,12 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -260,9 +259,9 @@ checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -284,7 +283,7 @@ checksum = "a9dbbdc4b4d349732bc6690de10a9de952bd39ba6a065c586e26600b6b0b91f5" dependencies = [ "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -295,9 +294,9 @@ checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -470,9 +469,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deranged" @@ -517,7 +516,7 @@ dependencies = [ [[package]] name = "docs_macro" -version = "0.4.0" +version = "0.4.1" dependencies = [ "serde", "syntect", @@ -606,15 +605,15 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -752,9 +751,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -1390,9 +1389,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1430,7 +1429,7 @@ dependencies = [ "server_fn", "slotmap", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", "typed-builder 0.23.2", "typed-builder-macro 0.23.2", @@ -1473,7 +1472,7 @@ dependencies = [ "config", "regex", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "typed-builder 0.21.2", ] @@ -1582,7 +1581,7 @@ dependencies = [ "rustc_version", "send_wrapper", "tachys", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "wasm-bindgen", "web-sys", @@ -1779,9 +1778,9 @@ checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num_cpus" @@ -1800,7 +1799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" dependencies = [ "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2048,9 +2047,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2079,9 +2078,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2136,9 +2135,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -2163,7 +2162,7 @@ dependencies = [ "send_wrapper", "serde", "slotmap", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-sys", ] @@ -2285,7 +2284,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2303,7 +2302,7 @@ dependencies = [ "quote", "syn", "syn_derive", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2349,18 +2348,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -2503,7 +2502,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2554,7 +2553,7 @@ dependencies = [ "serde_json", "serde_qs", "server_fn_macro_default", - "thiserror 2.0.17", + "thiserror 2.0.18", "throw_error", "tokio", "tower", @@ -2641,7 +2640,7 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "singlestage" -version = "0.4.0" +version = "0.4.1" dependencies = [ "leptos", "leptos_meta", @@ -2655,7 +2654,7 @@ dependencies = [ [[package]] name = "singlestage_docs" -version = "0.4.0" +version = "0.4.1" dependencies = [ "axum", "console_error_panic_hook", @@ -2714,9 +2713,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -2799,7 +2798,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "yaml-rust", ] @@ -2883,11 +2882,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2903,9 +2902,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2923,30 +2922,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -3074,9 +3073,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -3168,7 +3167,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] @@ -3280,9 +3279,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -3328,18 +3327,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -3350,11 +3349,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -3363,9 +3363,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3373,9 +3373,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -3386,9 +3386,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -3430,9 +3430,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -3649,9 +3649,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -3705,18 +3705,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" dependencies = [ "proc-macro2", "quote", @@ -3785,9 +3785,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" [[package]] name = "zstd" diff --git a/Cargo.toml b/Cargo.toml index 55e9639c..f3ca2968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["singlestage", "docs", "singlestage_macro", "docs_macro"] [workspace.package] -version = "0.4.0" +version = "0.4.1" authors = ["Adam Doyle "] edition = "2024" rust-version = "1.88" diff --git a/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs b/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs index 3456a36a..8aa31099 100644 --- a/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs +++ b/docs/src/routes/components/breadcrumb/examples/breadcrumb.rs @@ -14,10 +14,7 @@ pub fn BreadcrumbExample() -> impl IntoView { - diff --git a/docs/src/routes/components/button_group/examples/button_group.rs b/docs/src/routes/components/button_group/examples/button_group.rs index 287bc6ed..74193d0a 100644 --- a/docs/src/routes/components/button_group/examples/button_group.rs +++ b/docs/src/routes/components/button_group/examples/button_group.rs @@ -22,7 +22,7 @@ pub fn ButtonGroupExample() -> impl IntoView { {icon!(icondata::FiMoreHorizontal)} - + {icon!(icondata::LuMailCheck)} "Mark as Read" diff --git a/docs/src/routes/components/button_group/examples/button_group_dropdown.rs b/docs/src/routes/components/button_group/examples/button_group_dropdown.rs index e6af1936..07bdc154 100644 --- a/docs/src/routes/components/button_group/examples/button_group_dropdown.rs +++ b/docs/src/routes/components/button_group/examples/button_group_dropdown.rs @@ -8,11 +8,9 @@ pub fn ButtonGroupDropdownExample() -> impl IntoView { - + - + {icon!(icondata::BiVolumeMuteRegular)} "Mute Conversation" diff --git a/docs/src/routes/components/button_group/examples/button_group_popover.rs b/docs/src/routes/components/button_group/examples/button_group_popover.rs index af5899cc..9e3454ff 100644 --- a/docs/src/routes/components/button_group/examples/button_group_popover.rs +++ b/docs/src/routes/components/button_group/examples/button_group_popover.rs @@ -12,20 +12,22 @@ pub fn ButtonGroupPopoverExample() -> impl IntoView { {icon!(icondata::LuChevronDown)} - -

"Agent Tasks"

- -
- - + + + + - - "Share your thoughts about our service." - - - - -
+
+ + + + - +
+ +
+ } +} diff --git a/docs/src/routes/components/label/examples/labels_in_fields.rs b/docs/src/routes/components/label/examples/labels_in_fields.rs new file mode 100644 index 00000000..c252c819 --- /dev/null +++ b/docs/src/routes/components/label/examples/labels_in_fields.rs @@ -0,0 +1,97 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn LabelsInFieldsExample() -> impl IntoView { + view! { +
+
+ +
+ "Payment Method" + + "All transactions are secure and encrypted" + + + + + + + + + + + "Enter your 16-digit card number" + + +
+ + + + + + + + + + + + +
+
+
+ +
+ "Billing Address" + + "The billing address associated with your payment method" + + + + + + + +
+
+ + + + + +

"Field:"

+
+ + "label" + + + "label" + + + "label" + + + "label" + + + + +
+ } +} diff --git a/docs/src/routes/debug/mod.rs b/docs/src/routes/debug/mod.rs index e7efd7cc..fdb187f2 100644 --- a/docs/src/routes/debug/mod.rs +++ b/docs/src/routes/debug/mod.rs @@ -6,6 +6,7 @@ mod context_menu; mod dropdown; mod form_reset; mod input; +mod label; mod radio; mod select; mod slider; @@ -18,6 +19,7 @@ use context_menu::*; use dropdown::*; use form_reset::*; use input::*; +use label::*; use radio::*; use select::*; use slider::*; @@ -39,6 +41,7 @@ pub fn ReactiveDebug() -> impl IntoView { +
} } diff --git a/docs/src/routes/debug/radio.rs b/docs/src/routes/debug/radio.rs index 323a22fb..9dd43261 100644 --- a/docs/src/routes/debug/radio.rs +++ b/docs/src/routes/debug/radio.rs @@ -26,11 +26,20 @@ pub fn DebugRadio() -> impl IntoView { + "Group" "One" "Two" "Three" + + "Solo" + + + + "in Field" + + - - From e9deadbe2cf2019651f6abd79185741528d12f0a Mon Sep 17 00:00:00 2001 From: Adam Doyle Date: Mon, 13 Apr 2026 16:24:43 -0400 Subject: [PATCH 058/120] better signal handing/reactivity, lower latency --- Cargo.lock | 29 +++--- .../routes/components/button/component.toml | 2 + singlestage/build.rs | 2 +- singlestage/src/components/button/mod.rs | 33 ++++++- .../button/style/base/vega/button.css | 3 +- .../src/components/button/style/button.css | 6 +- .../components/button/style/button_dark.css | 3 +- singlestage/src/components/input/mod.rs | 90 ++++++++---------- singlestage/src/components/textarea/mod.rs | 91 +++++++------------ singlestage/src/lib.rs | 4 +- singlestage/src/primitives/dialog/content.rs | 2 +- 11 files changed, 127 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d27e7701..51b76df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,15 +1022,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "c2b52f86d1d4bc0d6b4e6826d960b1b333217e07d36b882dca570a5e1c48895b" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1701,9 +1700,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.184" +version = "0.2.185" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" [[package]] name = "linked-hash-map" @@ -1896,9 +1895,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" dependencies = [ "bitflags", "cfg-if", @@ -1928,9 +1927,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" dependencies = [ "cc", "libc", @@ -2009,9 +2008,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plist" @@ -2169,9 +2168,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha", "rand_core", @@ -2439,9 +2438,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "once_cell", "rustls-pki-types", diff --git a/docs/src/routes/components/button/component.toml b/docs/src/routes/components/button/component.toml index c9bc7094..67d5f1d9 100644 --- a/docs/src/routes/components/button/component.toml +++ b/docs/src/routes/components/button/component.toml @@ -68,6 +68,8 @@ attributes.""" attrs = [ { attr = "button_type", attr_type = "String", default = """"submit"""", description = """Renamed - - - + + + + "Analytics" + + "Track performance and user engagement metrics. Monitor trends and + identify growth opportunities." + + + + "Page views are up 25% compared to last month." + + + + + + + "Reports" + + "Generate and download your detailed reports. Export data in + multiple formats for analysis." + + + + "You have 5 reports ready and available to export." + + + + - "Password" + "Settings" - "Change your password here. After saving, you'll be logged out." + "Manage your account preferences and options. Customize your + experience to fit your needs." - -
-
- - -
-
- - -
-
+ + "Configure notifications, security, and themes." - - -
diff --git a/docs/src/routes/components/tabs/examples/tabs_disabled.rs b/docs/src/routes/components/tabs/examples/tabs_disabled.rs new file mode 100644 index 00000000..3f1488a9 --- /dev/null +++ b/docs/src/routes/components/tabs/examples/tabs_disabled.rs @@ -0,0 +1,16 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TabsDisabledExample() -> impl IntoView { + view! { + + + "Home" + + "Disabled" + + + + } +} diff --git a/docs/src/routes/components/tabs/examples/tabs_icons.rs b/docs/src/routes/components/tabs/examples/tabs_icons.rs new file mode 100644 index 00000000..29f3975c --- /dev/null +++ b/docs/src/routes/components/tabs/examples/tabs_icons.rs @@ -0,0 +1,14 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TabsIconsExample() -> impl IntoView { + view! { + + + {icon!(icondata::LuAppWindow)} "Preview" + {icon!(icondata::LuCode)} "Code" + + + } +} diff --git a/docs/src/routes/components/tabs/examples/tabs_line.rs b/docs/src/routes/components/tabs/examples/tabs_line.rs new file mode 100644 index 00000000..1d29a1b2 --- /dev/null +++ b/docs/src/routes/components/tabs/examples/tabs_line.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TabsLineExample() -> impl IntoView { + view! { + + + "Overview" + "Analytics" + "Reports" + + + } +} diff --git a/docs/src/routes/components/tabs/examples/tabs_vertical.rs b/docs/src/routes/components/tabs/examples/tabs_vertical.rs new file mode 100644 index 00000000..d67833d0 --- /dev/null +++ b/docs/src/routes/components/tabs/examples/tabs_vertical.rs @@ -0,0 +1,15 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn TabsVerticalExample() -> impl IntoView { + view! { + + + "Account" + "Password" + "Notifications" + + + } +} diff --git a/singlestage/src/components/tabs/content.rs b/singlestage/src/components/tabs/content.rs index 7be3c2bc..f4f24b0a 100644 --- a/singlestage/src/components/tabs/content.rs +++ b/singlestage/src/components/tabs/content.rs @@ -117,12 +117,22 @@ pub fn TabsContent( ) -> impl IntoView { let tabs = expect_context::(); + let tab_selected = move || { + if let Some(value) = value.get() + && value == tabs.value.get() + { + true + } else { + false + } + }; + let global_attrs_1 = view! { <{..} accesskey=move || accesskey.get() autocapitalize=move || autocapitalize.get() autofocus=move || autofocus.get() - class=move || class.get() + // class=move || class.get() contenteditable=move || contenteditable.get() dir=move || dir.get() draggable=move || draggable.get() @@ -159,14 +169,15 @@ pub fn TabsContent( view! { - "Viewer" "Developer" "Billing" @@ -41,7 +41,7 @@ pub fn TeamMembers() -> impl IntoView {

"p@example.com"

- "Empty" "Viewer" "Developer" @@ -61,7 +61,7 @@ pub fn TeamMembers() -> impl IntoView {

"i@example.com"

- "Viewer" "Developer" "Billing" diff --git a/singlestage/src/components/carousel/carousel.rs b/singlestage/src/components/carousel/carousel.rs index 2835328f..20cdb8de 100644 --- a/singlestage/src/components/carousel/carousel.rs +++ b/singlestage/src/components/carousel/carousel.rs @@ -135,7 +135,7 @@ pub fn Carousel( } } - if let Some(ul_ref) = ul_ref.get_untracked() { + if let Some(ul_ref) = ul_ref.get() { let width = ul_ref.scroll_width(); let scroll_step = width / num_items; diff --git a/singlestage/src/components/popover/content.rs b/singlestage/src/components/popover/content.rs index be455ef1..6f8da8bb 100644 --- a/singlestage/src/components/popover/content.rs +++ b/singlestage/src/components/popover/content.rs @@ -122,7 +122,7 @@ pub fn PopoverContent( let menu_ref = NodeRef::::new(); Effect::new(move || { - if let Some(popover) = menu_ref.get_untracked() { + if let Some(popover) = menu_ref.get() { let _ = popover.toggle_popover_with_force(menu.open.get()); } }); diff --git a/singlestage/src/components/select/select.rs b/singlestage/src/components/select/select.rs index b79af381..cae50efb 100644 --- a/singlestage/src/components/select/select.rs +++ b/singlestage/src/components/select/select.rs @@ -187,14 +187,14 @@ pub fn Select( let context = SelectContext { multiple, value }; Effect::new(move || { - if let Some(select) = select_ref.get_untracked() { + if let Some(select) = select_ref.get() { select.set_disabled(disabled.get()); } }); // Update value reactively Effect::new(move || { - if let Some(select) = select_ref.get_untracked() { + if let Some(select) = select_ref.get() { let value = value.get(); if !value.is_empty() { select.set_value(&value); diff --git a/singlestage/src/components/sheet/content.rs b/singlestage/src/components/sheet/content.rs index 15db8058..13544360 100644 --- a/singlestage/src/components/sheet/content.rs +++ b/singlestage/src/components/sheet/content.rs @@ -117,7 +117,7 @@ pub fn SheetContent( let sheet_ref = NodeRef::::new(); Effect::new(move || { - if let Some(overlay) = overlay_ref.get_untracked() { + if let Some(overlay) = overlay_ref.get() { match sheet_context.open.get() { true => { let _ = overlay.show_modal(); diff --git a/singlestage/src/components/tooltip/content.rs b/singlestage/src/components/tooltip/content.rs new file mode 100644 index 00000000..d9d0dfd8 --- /dev/null +++ b/singlestage/src/components/tooltip/content.rs @@ -0,0 +1,197 @@ +use crate::TooltipContext; +use leptos::prelude::*; + +#[component] +pub fn TooltipContent( + children: Children, + + /// Set where the tooltip is rendered along the chosen side. + /// + /// Accepted values: "start" | "center" | "end". Default is "center". + #[prop(optional, into)] + align: MaybeProp, + /// Set which side of the triggering element that the tooltip will spawn from. + /// + /// Accepted values: "top" | "right" | "bottom" | "left". Default is "top". + #[prop(optional, into)] + side: MaybeProp, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `
` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let tooltip = expect_context::(); + let content_ref = NodeRef::::new(); + + Effect::new(move || { + if let Some(content) = content_ref.get() { + let _ = content.toggle_popover_with_force(tooltip.open.get()); + } + }); + + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
"singlestage-popover-right", + "bottom" => "singlestage-popover-bottom", + "left" => "singlestage-popover-left", + _ => "singlestage-popover-top", + }, + match align.get().unwrap_or_default().as_str() { + "start" => "singlestage-popover-start", + "end" => "singlestage-popover-end", + _ => "singlestage-popover-center", + }, + class.get().unwrap_or_default(), + ) + } + node_ref=content_ref + popover="manual" + style:position-anchor=move || format!("--{}", tooltip.trigger_id.get()) + + {..global_attrs_1} + {..global_attrs_2} + > + {children()} +
+ } +} diff --git a/singlestage/src/components/tooltip/mod.rs b/singlestage/src/components/tooltip/mod.rs index 9c73e254..ca6934da 100644 --- a/singlestage/src/components/tooltip/mod.rs +++ b/singlestage/src/components/tooltip/mod.rs @@ -1,184 +1,15 @@ -use leptos::prelude::*; - -/// A popup that displays information related to an element when the element receives keyboard -/// focus or the mouse hovers over it. -#[component] -pub fn Tooltip( - children: Children, - - /// Set where the tooltip is rendered along the chosen side. - /// - /// Accepted values: "start" | "center" | "end". Default is "center". - #[prop(optional, into)] - align: MaybeProp, - /// Set which side of the triggering element that the tooltip will spawn from. - /// - /// Accepted values: "top" | "bottom" | "left" | "right". Default is "top". - #[prop(optional, into)] - side: MaybeProp, - #[prop(optional, into)] value: Signal, +mod content; +mod tooltip; +mod trigger; - // GLOBAL ATTRIBUTES - // - /// A space separated list of keys to focus this element. The first key available on the user's - /// keyboard layout is used. - #[prop(optional, into)] - accesskey: MaybeProp, - /// Sets whether the input value should be capitalized and how. If a parent `` has - /// `autocapitalize` rules set, it will override any rules set here. - /// - /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". - #[prop(optional, into)] - autocapitalize: MaybeProp, - /// Grabs focus once the page has finished loading. Only one element on the page can be focused - /// at a time. - #[prop(optional, into)] - autofocus: MaybeProp, - /// Apply classes to the element. - #[prop(optional, into)] - class: MaybeProp, - /// Allows client-side editing of the element by the user. - /// - /// Accepted values: "true" | "false" | "plaintext-only" - #[prop(optional, into)] - contenteditable: MaybeProp, - /// Indicate directionality of the element's text. - /// - /// Accepted values: "ltr" | "rtl" | "auto" - #[prop(optional, into)] - dir: MaybeProp, - /// Toggle whether the element can be dragged. - #[prop(optional, into)] - draggable: MaybeProp, - /// Modifies the appearance of the enter key on virtual keyboards. - #[prop(optional, into)] - enterkeyhint: MaybeProp, - /// Expose elements in the shadow DOM to be manipulated by the DOM. - #[prop(optional, into)] - exportparts: MaybeProp, - /// Controls hidden status of the element. - #[prop(optional, into)] - hidden: MaybeProp, - /// Set the id of this element. - #[prop(optional, into)] - id: MaybeProp, - /// Toggle if the browser reacts to input events from this element. - #[prop(optional, into)] - inert: MaybeProp, - /// Hints to the browser of what type of virtual keyboard to display when editing this element - /// or its children. - #[prop(optional, into)] - inputmode: MaybeProp, - /// Used to render a standard element as a custom element. - #[prop(optional, into)] - is: MaybeProp, - /// Unique global identifier of an item. - #[prop(optional, into)] - itemid: MaybeProp, - /// Used to add properties to an item. - #[prop(optional, into)] - itemprop: MaybeProp, - /// Used to associate an item with a related non-parent element that's using `itemscope`. - #[prop(optional, into)] - itemref: MaybeProp, - /// Used to declare that children elements are related to a particular item. - #[prop(optional, into)] - itemscope: MaybeProp, - /// URL of data used to define `itemprops`. - #[prop(optional, into)] - itemtype: MaybeProp, - /// Defines the language of an element. - #[prop(optional, into)] - lang: MaybeProp, - /// Cryptographic "number used once". - #[prop(optional, into)] - nonce: MaybeProp, - /// List of the part names of the element. - #[prop(optional, into)] - part: MaybeProp, - /// Designate an element as a popover element. - #[prop(optional, into)] - popover: MaybeProp, - /// Define the semantic meaning of content. - #[prop(optional, into)] - role: MaybeProp, - /// Assigns a slot to an element. - #[prop(optional, into)] - slot: MaybeProp, - /// Toggle spellcheck for this input. - /// - /// Accepted values: "default" | "true" | "false". - #[prop(optional, into)] - spellcheck: MaybeProp, - /// Define CSS to be applied to the element. - #[prop(optional, into)] - style: MaybeProp, - /// Controls how an element behaves when a user navigates using the tab key. - #[prop(optional, into)] - tabindex: MaybeProp, - /// Describes the content of the element to screen readers. - #[prop(optional, into)] - title: MaybeProp, - /// Defines localization behavior for the element. - #[prop(optional, into)] - translate: MaybeProp, -) -> impl IntoView { - let global_attrs_1 = view! { - <{..} - accesskey=move || accesskey.get() - autocapitalize=move || autocapitalize.get() - autofocus=move || autofocus.get() - class=move || class.get() - contenteditable=move || contenteditable.get() - dir=move || dir.get() - draggable=move || draggable.get() - enterkeyhint=move || enterkeyhint.get() - exportparts=move || exportparts.get() - hidden=move || hidden.get() - id=move || id.get() - inert=move || inert.get() - inputmode=move || inputmode.get() - is=move || is.get() - itemid=move || itemid.get() - /> - }; +pub use content::*; +pub use tooltip::*; +pub use trigger::*; - let global_attrs_2 = view! { - <{..} - itemprop=move || itemprop.get() - itemref=move || itemref.get() - itemscope=move || itemscope.get() - itemtype=move || itemtype.get() - lang=move || lang.get() - nonce=move || nonce.get() - part=move || part.get() - popover=move || popover.get() - role=move || role.get() - slot=move || slot.get() - spellcheck=move || spellcheck.get() - style=move || style.get() - tabindex=move || tabindex.get() - title=move || title.get() - translate=move || translate.get() - /> - }; - - view! { -
- {children()} -
- } +#[derive(Clone)] +pub(crate) struct TooltipContext { + open: RwSignal, + trigger_id: RwSignal, } diff --git a/singlestage/src/components/tooltip/style/base/vega/tooltip.css b/singlestage/src/components/tooltip/style/base/vega/tooltip.css new file mode 100644 index 00000000..e6b8826d --- /dev/null +++ b/singlestage/src/components/tooltip/style/base/vega/tooltip.css @@ -0,0 +1,38 @@ +@layer base { + .singlestage-tooltip-content { + @apply gap-1.5 + inline-flex + items-center + px-3 + py-1.5 + rounded-md + text-xs; + + &:has(.singlestage-kbd) { + @apply pr-1.5; + } + + .singlestage-kbd { + @apply isolate + relative + rounded-sm + z-50; + } + } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply rotate-45 */ + /* rounded-[2px] */ + /* size-2.5 */ + /* translate-y-[calc(-50%-2px)]; */ + /* } */ + /* .singlestage-tooltip-arrow-logical { */ + /* @apply data-[side=inline-end]:-left-1 */ + /* data-[side=inline-end]:-translate-y-1/2 */ + /* data-[side=inline-end]:top-1/2! */ + /* data-[side=inline-start]:-right-1 */ + /* data-[side=inline-start]:-translate-y-1/2 */ + /* data-[side=inline-start]:top-1/2!; */ + /* } */ +} diff --git a/singlestage/src/components/tooltip/style/tooltip.css b/singlestage/src/components/tooltip/style/tooltip.css index 956ae8fd..f7b5cc18 100644 --- a/singlestage/src/components/tooltip/style/tooltip.css +++ b/singlestage/src/components/tooltip/style/tooltip.css @@ -1,102 +1,38 @@ @layer components { - @media (hover: hover) and (pointer: fine) { - [data-tooltip] { - @apply relative - w-fit; - - &:before { - @apply absolute - bg-primary - content-[attr(data-tooltip)] - invisible - max-w-xs - opacity-0 - pointer-events-none - px-3 - py-1.5 - rounded-md - scale-95 - text-primary-foreground - text-xs - transition-all - truncate - w-fit - z-[60]; - } - - &:hover:before { - @apply opacity-100 - scale-100 - visible; - } - - &:focus-visible:not(:hover):before { - @apply hidden; - } - - &:not([data-side]), - &[data-side="top"] { - @apply before:bottom-full - before:mb-1.5 - before:translate-y-2 - hover:before:translate-y-0; - } - - &[data-side="bottom"] { - @apply before:-translate-y-2 - before:mt-1.5 - before:top-full - hover:before:translate-y-0; - } - - &:not([data-side]), - &[data-side="top"], - &[data-side="bottom"] { - &[data-align="start"] { - @apply before:left-0; - } - - &[data-align="end"] { - @apply before:right-0; - } - - &:not([data-align]), - &[data-align="center"] { - @apply before:-translate-x-1/2 - before:left-1/2; - } - } - - &[data-side="left"] { - @apply before:mr-1.5 - before:right-full - before:translate-x-2 - hover:before:translate-x-0; - } - - &[data-side="right"] { - @apply before:-translate-x-2 - before:left-full - before:ml-1.5 - hover:before:translate-x-0; - } - - &[data-side="left"], - &[data-side="right"] { - &[data-align="start"] { - @apply before:top-0; - } - - &[data-align="end"] { - @apply before:bottom-0; - } - - &:not([data-align]), - &[data-align="center"] { - @apply before:-translate-y-1/2 - before:top-1/2; - } - } + .singlestage-tooltip-content { + @apply bg-foreground + max-w-xs + text-background + w-fit + z-50; + + /* TODO: Arrows */ + &.singlestage-popover-top { + @apply mb-1.5; + } + &.singlestage-popover-right { + @apply ml-1.5; + } + &.singlestage-popover-bottom { + @apply mt-1.5; + } + &.singlestage-popover-left { + @apply mr-1.5; } } + + /* TODO: Arrows */ + /* .singlestage-tooltip-arrow { */ + /* @apply bg-foreground */ + /* data-[side=bottom]:top-1 */ + /* data-[side=left]:-right-1 */ + /* data-[side=left]:-translate-y-1/2 */ + /* data-[side=left]:top-1/2! */ + /* data-[side=right]:-left-1 */ + /* data-[side=right]:-translate-y-1/2 */ + /* data-[side=right]:top-1/2! */ + /* data-[side=top]:-bottom-2.5 */ + /* fill-foreground */ + /* z-50; */ + /* } */ } diff --git a/singlestage/src/components/tooltip/tooltip.rs b/singlestage/src/components/tooltip/tooltip.rs new file mode 100644 index 00000000..559c6771 --- /dev/null +++ b/singlestage/src/components/tooltip/tooltip.rs @@ -0,0 +1,170 @@ +use crate::TooltipContext; +use leptos::{context::Provider, prelude::*}; + +/// A popup that displays information related to an element when the element receives keyboard +/// focus or the mouse hovers over it. +#[component] +pub fn Tooltip( + children: Children, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let trigger_id = RwSignal::new("tooltip-trigger".to_string()); + let open = RwSignal::new(false); + + let context = TooltipContext { open, trigger_id }; + + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + class=move || class.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + id=move || id.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
+ {children()} +
+ } +} diff --git a/singlestage/src/components/tooltip/trigger.rs b/singlestage/src/components/tooltip/trigger.rs new file mode 100644 index 00000000..d044e638 --- /dev/null +++ b/singlestage/src/components/tooltip/trigger.rs @@ -0,0 +1,171 @@ +use crate::TooltipContext; +use leptos::prelude::*; + +#[component] +pub fn TooltipTrigger( + children: Children, + + // GLOBAL ATTRIBUTES + // + /// A space separated list of keys to focus this element. The first key available on the user's + /// keyboard layout is used. + #[prop(optional, into)] + accesskey: MaybeProp, + /// Sets whether the input value should be capitalized and how. If a parent `` has + /// `autocapitalize` rules set, it will override any rules set here. + /// + /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters". + #[prop(optional, into)] + autocapitalize: MaybeProp, + /// Grabs focus once the page has finished loading. Only one element on the page can be focused + /// at a time. + #[prop(optional, into)] + autofocus: MaybeProp, + /// Apply classes to the element. + #[prop(optional, into)] + class: MaybeProp, + /// Allows client-side editing of the element by the user. + /// + /// Accepted values: "true" | "false" | "plaintext-only" + #[prop(optional, into)] + contenteditable: MaybeProp, + /// Indicate directionality of the element's text. + /// + /// Accepted values: "ltr" | "rtl" | "auto" + #[prop(optional, into)] + dir: MaybeProp, + /// Toggle whether the element can be dragged. + #[prop(optional, into)] + draggable: MaybeProp, + /// Modifies the appearance of the enter key on virtual keyboards. + #[prop(optional, into)] + enterkeyhint: MaybeProp, + /// Expose elements in the shadow DOM to be manipulated by the DOM. + #[prop(optional, into)] + exportparts: MaybeProp, + /// Controls hidden status of the element. + #[prop(optional, into)] + hidden: MaybeProp, + /// Set the id of this element. + #[prop(optional, into)] + id: MaybeProp, + /// Toggle if the browser reacts to input events from this element. + #[prop(optional, into)] + inert: MaybeProp, + /// Hints to the browser of what type of virtual keyboard to display when editing this element + /// or its children. + #[prop(optional, into)] + inputmode: MaybeProp, + /// Used to render a standard element as a custom element. + #[prop(optional, into)] + is: MaybeProp, + /// Unique global identifier of an item. + #[prop(optional, into)] + itemid: MaybeProp, + /// Used to add properties to an item. + #[prop(optional, into)] + itemprop: MaybeProp, + /// Used to associate an item with a related non-parent element that's using `itemscope`. + #[prop(optional, into)] + itemref: MaybeProp, + /// Used to declare that children elements are related to a particular item. + #[prop(optional, into)] + itemscope: MaybeProp, + /// URL of data used to define `itemprops`. + #[prop(optional, into)] + itemtype: MaybeProp, + /// Defines the language of an element. + #[prop(optional, into)] + lang: MaybeProp, + /// Cryptographic "number used once". + #[prop(optional, into)] + nonce: MaybeProp, + /// List of the part names of the element. + #[prop(optional, into)] + part: MaybeProp, + /// Designate an element as a popover element. + #[prop(optional, into)] + popover: MaybeProp, + /// Define the semantic meaning of content. + #[prop(optional, into)] + role: MaybeProp, + /// Assigns a slot to an element. + #[prop(optional, into)] + slot: MaybeProp, + /// Toggle spellcheck for this input. + /// + /// Accepted values: "default" | "true" | "false". + #[prop(optional, into)] + spellcheck: MaybeProp, + /// Define CSS to be applied to the element. + #[prop(optional, into)] + style: MaybeProp, + /// Controls how an element behaves when a user navigates using the tab key. + #[prop(optional, into)] + tabindex: MaybeProp, + /// Describes the content of the element to screen readers. + #[prop(optional, into)] + title: MaybeProp, + /// Defines localization behavior for the element. + #[prop(optional, into)] + translate: MaybeProp, +) -> impl IntoView { + let tooltip = expect_context::(); + + let global_attrs_1 = view! { + <{..} + accesskey=move || accesskey.get() + autocapitalize=move || autocapitalize.get() + autofocus=move || autofocus.get() + contenteditable=move || contenteditable.get() + dir=move || dir.get() + draggable=move || draggable.get() + enterkeyhint=move || enterkeyhint.get() + exportparts=move || exportparts.get() + hidden=move || hidden.get() + inert=move || inert.get() + inputmode=move || inputmode.get() + is=move || is.get() + itemid=move || itemid.get() + /> + }; + + let global_attrs_2 = view! { + <{..} + itemprop=move || itemprop.get() + itemref=move || itemref.get() + itemscope=move || itemscope.get() + itemtype=move || itemtype.get() + lang=move || lang.get() + nonce=move || nonce.get() + part=move || part.get() + popover=move || popover.get() + role=move || role.get() + slot=move || slot.get() + spellcheck=move || spellcheck.get() + style=move || style.get() + tabindex=move || tabindex.get() + title=move || title.get() + translate=move || translate.get() + /> + }; + + view! { +
+ {children()} +
+ } +} diff --git a/test_site/Cargo.lock b/test_site/Cargo.lock index c7244319..e2e57f72 100644 --- a/test_site/Cargo.lock +++ b/test_site/Cargo.lock @@ -6036,9 +6036,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" dependencies = [ "bytes", "libc", @@ -6755,9 +6755,9 @@ dependencies = [ [[package]] name = "wasm_split_helpers" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a114b3073258dd5de3d812cdd048cca6842342755e828a14dbf15f843f2d1b84" +checksum = "d0cb6d1008be3c4c5abc31a407bfb8c8449ae14efc8561c1db821f79b9614b0a" dependencies = [ "async-once-cell", "wasm_split_macros", @@ -6765,9 +6765,9 @@ dependencies = [ [[package]] name = "wasm_split_macros" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56481f8ed1a9f9ae97ea7b08a5e2b12e8adf9a7818a6ba952b918e09c7be8bf0" +checksum = "d0a659ffe5c7f4538aa6357c07e3d73221cc61eba03bd9a081e14bc91ed09b8c" dependencies = [ "base16", "quote", diff --git a/test_site/app/src/debug/mod.rs b/test_site/app/src/debug/mod.rs index e53e90ff..7f4829ad 100644 --- a/test_site/app/src/debug/mod.rs +++ b/test_site/app/src/debug/mod.rs @@ -12,6 +12,7 @@ mod select; mod slider; mod textarea; mod toggle; +mod tooltip; pub use button::*; pub use checkbox::*; @@ -25,6 +26,7 @@ pub use select::*; pub use slider::*; pub use textarea::*; pub use toggle::*; +pub use tooltip::*; #[component] pub fn DebugAll() -> impl IntoView { @@ -42,6 +44,7 @@ pub fn DebugAll() -> impl IntoView { + } } diff --git a/test_site/app/src/debug/tooltip.rs b/test_site/app/src/debug/tooltip.rs new file mode 100644 index 00000000..c7795e79 --- /dev/null +++ b/test_site/app/src/debug/tooltip.rs @@ -0,0 +1,72 @@ +use leptos::prelude::*; +use singlestage::*; + +#[component] +pub fn DebugTooltip() -> impl IntoView { + view! { +

"Tooltip"

+ +
+
+ + + + + + +

"Add to library"

+
+
+
+
+ +
+ +
+ + + + + + +

"Add to library"

+
+
+
+
+
+
+ +
+ + + + + + +

"Add to library"

+
+
+
+
+
+ } +} diff --git a/test_site/app/src/lib.rs b/test_site/app/src/lib.rs index 9b2d33f9..33352f4d 100644 --- a/test_site/app/src/lib.rs +++ b/test_site/app/src/lib.rs @@ -36,25 +36,30 @@ pub fn SidebarButton() -> impl IntoView { let sidebar = expect_context::(); view! { - - + view! { {icon!(icondata::LuPanelRightClose)} }.into_any(), + _ => view! { {icon!(icondata::LuPanelLeftClose)} }.into_any(), + } + > + {match sidebar.side.get().as_str() { + "right" => view! { {icon!(icondata::LuPanelRightOpen)} }.into_any(), + _ => view! { {icon!(icondata::LuPanelLeftOpen)} }.into_any(), + }} + + + + +

"Toggle sidebar"

+
} } @@ -75,6 +80,7 @@ pub fn SidebarComponent() -> impl IntoView { "Slider", "Textarea", "Toggle", + "Tooltip", ]); view! { @@ -170,6 +176,7 @@ pub fn App() -> impl IntoView { + From 1473c8fd6ce70c3a2079931ef19970683a5fe239 Mon Sep 17 00:00:00 2001 From: Adam Doyle Date: Thu, 16 Apr 2026 22:33:46 -0400 Subject: [PATCH 067/120] set up modular base styles and hard code vega for now --- Cargo.lock | 53 +- docs/style/main.css | 8 + singlestage/Cargo.toml | 15 + singlestage/build.rs | 553 +++++++++--------- .../src/components/theme_provider/main.css | 19 - .../src/components/theme_provider/mod.rs | 37 +- singlestage/src/lib.rs | 35 ++ test_site/Cargo.lock | 48 +- 8 files changed, 405 insertions(+), 363 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 820bde41..2e3e9e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,9 +576,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "either_of" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f86eef3a7e4b9c2107583dbbbe3d9535c4b800796faf1774b82ba22033da" +checksum = "5060e0a4cbf26a87550792688ade88e6b8aec9208613631a7a363bda7bc2d4cd" dependencies = [ "paste", "pin-project-lite", @@ -1478,9 +1478,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "leptos" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b540ac2868724738f0f5d00f00ec4640e587223774219c1baddc46bad46fb8e" +checksum = "efa3982e7fe36c1de68f91f3c9083124f389a975523881f3d7e3363362feda41" dependencies = [ "any_spawner", "base64", @@ -1520,9 +1520,9 @@ dependencies = [ [[package]] name = "leptos_axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196de3f5cde6a4c4cd254bb16dc6abd2efbf46cc3ae1b6c7da0731f77b4bdf61" +checksum = "d2ac7734eed700b0170dffbfc93b03491ed1f306622d79625323a21ed0eedac0" dependencies = [ "any_spawner", "axum", @@ -1543,9 +1543,9 @@ dependencies = [ [[package]] name = "leptos_config" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a2ac32008dda0d657f2147cc33336f4e743e091597db10f7a99d668e92a46d" +checksum = "0c06f751315bccc0d193fab302ac01d25bcfcd97474d4676440e7e3250dc3fc3" dependencies = [ "config", "regex", @@ -1604,9 +1604,9 @@ dependencies = [ [[package]] name = "leptos_macro" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712325a77f1d050bf2897061ccaf2b075930aab36954980d658f04452686c474" +checksum = "9360df573fb57582384a8b7640a3de94ce6501d49be3b69f637cf11a42da484b" dependencies = [ "attribute-derive", "cfg-if", @@ -2217,9 +2217,9 @@ dependencies = [ [[package]] name = "reactive_graph" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35774620b3da884a07341e9e36612e1509b1eb0553ef3bb76f1547dd1b797417" +checksum = "00c5a025366836190c7030e883cc2bcd9e384ff555336e3c7954741ca411b177" dependencies = [ "any_spawner", "async-lock", @@ -2258,9 +2258,9 @@ dependencies = [ [[package]] name = "reactive_stores" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e114642d342893571ff40b4e1da8ccdea907be44c649041eb7d8413b3fd95e8" +checksum = "c30fd35b7d299c591293bb69fed47a703eb2703b1cff0493e78b16ed007e5382" dependencies = [ "guardian", "indexmap", @@ -2268,7 +2268,7 @@ dependencies = [ "or_poisoned", "paste", "reactive_graph", - "reactive_stores_macro 0.4.1", + "reactive_stores_macro 0.4.2", "rustc-hash", "send_wrapper", ] @@ -2288,9 +2288,9 @@ dependencies = [ [[package]] name = "reactive_stores_macro" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b024812c47a6867b6cb32767a46182203f94e59eb88c69b032fd9caffa304ce" +checksum = "e5d8e790a5ae5ddf9b7fa380c728375b06858e0cca7d063a73b3408320c523e1" dependencies = [ "convert_case 0.11.0", "proc-macro-error2", @@ -2631,9 +2631,9 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c799cec4e8e210dfb2f203aa97f0e82232c619e385ef4d011b17a58d6397c7b" +checksum = "5d60e4c1dfccd91fe0990141f69f1d5cf5679797ad53aa1b45e5bd658eb119f0" dependencies = [ "axum", "base64", @@ -2747,6 +2747,7 @@ version = "0.5.0" dependencies = [ "leptos", "leptos_meta", + "rayon", "reactive_stores 0.3.1", "reqwest", "serde", @@ -2930,9 +2931,9 @@ dependencies = [ [[package]] name = "tachys" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f768750b0d5514f487772187d4b20c66f56faff4541b1faa5aad4975f5aee085" +checksum = "2989c94c59db8497727875aa561d4d0daa3cc79b5774d5ced48263f7091beff1" dependencies = [ "any_spawner", "async-trait", @@ -2950,7 +2951,7 @@ dependencies = [ "or_poisoned", "paste", "reactive_graph", - "reactive_stores 0.4.2", + "reactive_stores 0.4.3", "rustc-hash", "rustc_version", "send_wrapper", @@ -3065,9 +3066,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -3354,9 +3355,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", diff --git a/docs/style/main.css b/docs/style/main.css index 7620e77d..69022126 100644 --- a/docs/style/main.css +++ b/docs/style/main.css @@ -15,6 +15,14 @@ } } +.singlestage-ulist { + @apply list-disc ml-6 my-6; +} + +.singlestage-olist { + @apply list-decimal ml-6 my-6; +} + /* donate button */ .kofi-btn { button { diff --git a/singlestage/Cargo.toml b/singlestage/Cargo.toml index c01b5fa8..d8a71b65 100644 --- a/singlestage/Cargo.toml +++ b/singlestage/Cargo.toml @@ -23,6 +23,7 @@ uuid = { workspace = true, features = ["v4", "js"] } web-sys = { workspace = true, features = ["DomRect", "Element"] } [build-dependencies] +rayon = { workspace = true } reqwest = { workspace = true, features = ["blocking"] } sha256 = { workspace = true } @@ -71,6 +72,13 @@ default = [ "skeleton", "slider", "spinner", + # "style_luma", + # "style_lyra", + # "style_maia", + # "style_mira", + # "style_nova", + # "style_sera", + "style_vega", "switch", "table", "tabs", @@ -118,6 +126,13 @@ sidebar = [] skeleton = [] slider = ["field", "label"] spinner = [] +style_luma = ["theme_provider"] +style_lyra = ["theme_provider"] +style_maia = ["theme_provider"] +style_mira = ["theme_provider"] +style_nova = ["theme_provider"] +style_sera = ["theme_provider"] +style_vega = ["theme_provider"] switch = ["field", "checkbox", "label"] table = [] tabs = [] diff --git a/singlestage/build.rs b/singlestage/build.rs index 16b915b5..1360b798 100644 --- a/singlestage/build.rs +++ b/singlestage/build.rs @@ -1,9 +1,4 @@ -#![allow(clippy::vec_init_then_push)] -/// Pre-build stuff -/// -/// This script snatches CSS per enabled component, merges it all to one file, -/// runs it through tailwind, and then minifies it. -/// +use rayon::prelude::*; use std::{ env, fs::{self, File, exists, remove_file}, @@ -12,41 +7,19 @@ use std::{ process::Command, }; -const TAILWIND_URL: &str = "https://github.com/tailwindlabs/tailwindcss/releases/download/v4.2.2/"; +const TAILWIND_URL: &str = "https://github.com/tailwindlabs/tailwindcss/releases/latest/download/"; -macro_rules! features { - ( $( $x:expr ),* ) => { - { - let mut features = vec![]; - $( - #[cfg(feature = $x)] - features.push($x); - )* - features - } - }; -} +fn tailwind_ready(path: &PathBuf) -> bool { + let output = Command::new(path).output(); -fn bundle_css(input: PathBuf, mut output: &File) { - let file = File::open(&input); - - if file.is_err() { - return; + if let Ok(output) = output { + output.status.success() + } else { + false } - - let mut buf_reader = BufReader::new(file.unwrap()); - let mut contents = String::new(); - - buf_reader - .read_to_string(&mut contents) - .unwrap_or_else(|_| panic!("Error reading {}", input.display())); - - output - .write_all(contents.as_bytes()) - .expect("Error writing bundle"); } -fn download_file(download_url: &str, file_path: &PathBuf) { +fn download_file(download_url: &str, file_path: &Path) { File::create(file_path).expect("Error creating file"); let mut file = File::options() @@ -70,47 +43,35 @@ fn download_file(download_url: &str, file_path: &PathBuf) { } fn run_tailwind( - tailwind_path: Option<&Path>, + tailwind_path: &PathBuf, bundle_path: &PathBuf, - bundle_dark_path: &PathBuf, - singlestage_path: &PathBuf, - singlestage_dark_path: &PathBuf, -) -> Result<(), ()> { - let output = Command::new(tailwind_path.unwrap_or(Path::new("tailwindcss"))) - .arg("-i") - .arg(bundle_path) - .arg("-o") - .arg(singlestage_path) - .arg("-m") - .output(); + output_path: &PathBuf, + css: String, +) { + let mut bundle = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&bundle_path) + .expect("\nError opening bundle file.\n"); + let _ = bundle.write_all(css.as_bytes()).unwrap(); - if let Ok(output) = output { - if !output.status.success() { - let error = String::from_utf8(output.stderr).unwrap(); - panic!("{}", error); - } - } else { - return Err(()); - } + let _cleanup = remove_file(&output_path); - let output_dark = Command::new(tailwind_path.unwrap_or(Path::new("tailwindcss"))) + let output = Command::new(&tailwind_path) .arg("-i") - .arg(bundle_dark_path) + .arg(bundle_path) .arg("-o") - .arg(singlestage_dark_path) + .arg(output_path) .arg("-m") .output(); - if let Ok(output_dark) = output_dark { - if !output_dark.status.success() { - let error = String::from_utf8(output_dark.stderr).unwrap(); - panic!("{}", error); - } - } else { - return Err(()); + if let Ok(output) = output + && !output.status.success() + { + let error = String::from_utf8(output.stderr).unwrap(); + panic!("{}", error); } - - Ok(()) } fn main() { @@ -119,20 +80,132 @@ fn main() { return; } - let out_dir = env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env. (1)\n"); - let bundle_path = Path::new(&out_dir).join("bundle.css"); - let bundle_dark_path = Path::new(&out_dir).join("bundle_dark.css"); + let out_dir = env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env.\n"); let singlestage_path = Path::new(&out_dir).join("singlestage.css"); - let singlestage_dark_path = Path::new(&out_dir).join("singlestage_dark.css"); - // Skip css bundling and tailwind for docs.rs if env::var("DOCS_RS").is_ok() { File::create(&singlestage_path).expect("\nError creating dummy file.\n"); return; } - // Build list of css files to include - let features = features!( + // Tailwind + println!("Checking for tailwind..."); + let mut tailwind_path: PathBuf = Path::new("tailwindcss").to_path_buf(); + + // User brought their own tailwind + if let Ok(user_path) = env::var("SINGLESTAGE_TAILWIND_PATH") { + println!("User supplied their own tailwind path..."); + + let path = Path::new(&user_path).to_path_buf(); + if tailwind_ready(&path) { + tailwind_path = path; + } else { + panic!( + "\nRunning tailwind at `{}` didn't work.\nIs it executable? (sudo chmod +x)\nIs this a full path?\n", + user_path + ); + } + }; + + // Try system tailwind + if !tailwind_ready(&tailwind_path) { + println!("System installed tailwind not found..."); + + let mut filename: String = String::from("tailwindcss"); + + match env::consts::OS { + "linux" => filename.push_str("-linux"), + "windows" => filename.push_str("-windows"), + "macos" => filename.push_str("-macos"), + _ => panic!("\nThis platform is not supported at this time.\n"), + }; + + match env::consts::ARCH { + "x86_64" => filename.push_str("-x64"), + "aarch64" => filename.push_str("-arm64"), + _ => panic!("\nThis platform is not supported at this time.\n"), + } + + // TODO: handle no windows aarch64 + if env::consts::OS == "windows" { + filename.push_str(".exe") + } + + // Try possibly already downloaded tailwind (rebuild) + tailwind_path = Path::new(&env::var_os("OUT_DIR").unwrap()).join(&filename); + if !tailwind_ready(&tailwind_path) { + println!("Cached downloaded tailwind not found..."); + + if !exists(&tailwind_path).expect("\nError checking for tailwind.\n") { + println!("Downloading tailwind..."); + + let file_url = format!("{}{}", &TAILWIND_URL, &filename); + download_file(&file_url, &tailwind_path); + } + + let checksums = Path::new(&env::var_os("OUT_DIR").unwrap()).join("sha256sums.txt"); + if !exists(&checksums).expect("\nError checking for checksums.\n") { + println!("Downloading checksums..."); + + let sums_url = format!("{}sha256sums.txt", &TAILWIND_URL); + download_file(&sums_url, &checksums); + } + + let sums = File::open(&checksums).expect("\nError opening checksums.\n"); + let buf_reader = BufReader::new(sums); + let mut expected_checksum = String::new(); + + for line in buf_reader.lines().map_while(Result::ok) { + let split_line = line.split_whitespace().collect::>(); + if format!("./{}", filename) == split_line[1] { + expected_checksum = split_line[0].into() + } + } + + let calculated_checksum = + sha256::try_digest(&tailwind_path).expect("\nError calculating checksum.\n"); + + println!("Expected Checksum: {}", expected_checksum); + println!("Calculated Checksum: {}", calculated_checksum); + + if expected_checksum == calculated_checksum { + println! {"Checksums match!"}; + } else { + println! {"Checksum mismatch!"}; + let _idc_if_this_fails = remove_file(tailwind_path); + panic!("\nChecksum mismatch!\n"); + } + + println!("Making tailwind executable..."); + if env::consts::FAMILY == "unix" { + Command::new("chmod") + .arg("+x") + .arg( + Path::new( + &env::var_os("OUT_DIR") + .expect("\nError reading OUT_DIR from env. (5)\n"), + ) + .join(&filename), + ) + .output() + .expect("\nError running chmod +x!\n"); + } + } + } + + if tailwind_ready(&tailwind_path) { + println!("Tailwind is ready!") + } else { + panic!( + "\nRunning tailwind at `{}` didn't work.\nIs it executable? (sudo chmod +x)\nIs this a full path?\n", + tailwind_path.display() + ); + } + + // Bundling + println!("Bundling CSS..."); + + let features = [ "accordion", "alert", "aspect_ratio", @@ -171,230 +244,126 @@ fn main() { "table", "tabs", "textarea", - "tooltip" - ); - - // Start merging css for selected features - let bundle = fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&bundle_path) - .expect("\nError opening bundle file.\n"); + "tooltip", + ]; - let bundle_dark = fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&bundle_dark_path) - .expect("\nError opening bundle file.\n"); + let base_styles = ["luma", "lyra", "maia", "mira", "nova", "sera", "vega"]; - // Theme provider goes first - #[cfg(feature = "theme_provider")] - bundle_css( - Path::new("src") + let mut css_raw = String::new(); + let _ = File::open( + &Path::new("src") .join("components") .join("theme_provider") .join("main.css"), - &bundle, - ); - #[cfg(feature = "theme_provider")] - bundle_css( - Path::new("src") - .join("components") - .join("theme_provider") - .join("main.css"), - &bundle_dark, - ); - #[cfg(feature = "theme_provider")] - bundle_css( - Path::new("src") + ) + .unwrap() + .read_to_string(&mut css_raw); + + let mut buf = String::new(); + let _ = File::open( + &Path::new("src") .join("components") .join("theme_provider") .join("main_dark.css"), - &bundle_dark, - ); - - // Bundle css for each feature - for feature in features { - let feature_flag = format!("CARGO_FEATURE_{}", feature.to_uppercase()); - - if env::var(&feature_flag).is_ok() { - let feature_base_css = Path::new("src") - .join("components") - .join(feature) - .join("style") - .join("base") - .join("vega") - .join(format!("{}.css", &feature)); - - let feature_base_dark_css = Path::new("src") - .join("components") - .join(feature) - .join("style") - .join("base") - .join("vega") - .join(format!("{}_dark.css", &feature)); - - let feature_css = Path::new("src") - .join("components") - .join(feature) - .join("style") - .join(format!("{}.css", &feature)); - - let feature_dark_css = Path::new("src") - .join("components") - .join(feature) - .join("style") - .join(format!("{}_dark.css", &feature)); - - bundle_css(feature_base_css, &bundle); - bundle_css(feature_base_dark_css, &bundle_dark); - bundle_css(feature_css, &bundle); - bundle_css(feature_dark_css, &bundle_dark); - } - } - - // Tailwind - - let _cleanup = remove_file(&singlestage_path); - - // User brought their own tailwind - if let Ok(tailwind_path) = env::var("SINGLESTAGE_TAILWIND_PATH") { - if run_tailwind( - Some(Path::new(&tailwind_path)), - &bundle_path, - &bundle_dark_path, - &singlestage_path, - &singlestage_dark_path, - ) - .is_ok() - { - // BYOT tailwind worked, bail - return; - } - panic!( - "\nRunning tailwind at `{}` didn't work.\nIs it executable? (sudo chmod +x)\nIs this a full path?\n", - tailwind_path - ) - } - - // Try system tailwind - if run_tailwind( - None, - &bundle_path, - &bundle_dark_path, - &singlestage_path, - &singlestage_dark_path, ) - .is_ok() - { - // System tailwind worked, bail - return; - } - - let mut filename: String = String::from("tailwindcss"); - - match env::consts::OS { - "linux" => filename.push_str("-linux"), - "windows" => filename.push_str("-windows"), - "macos" => filename.push_str("-macos"), - _ => panic!("\nThis platform is not supported at this time.\n"), - }; - - match env::consts::ARCH { - "x86_64" => filename.push_str("-x64"), - "aarch64" => filename.push_str("-arm64"), - _ => panic!("\nThis platform is not supported at this time.\n"), - } - - // TODO: handle no windows aarch64 - if env::consts::OS == "windows" { - filename.push_str(".exe") - } - - println!("Filename: {}", filename); - - // Try downloaded tailwind - let downloaded_tailwind_path = - Path::new(&env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env. (2)\n")) - .join(&filename); - if run_tailwind( - Some(&downloaded_tailwind_path), - &bundle_path, - &bundle_dark_path, - &singlestage_path, - &singlestage_dark_path, - ) - .is_ok() - { - // Downloaded tailwind worked, bail - return; - } - - let tailwind = - Path::new(&env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env. (3)\n")) - .join(&filename); - - if !exists(&tailwind).expect("\nError checking for tailwind.\n") { - let file_url = format!("{}{}", &TAILWIND_URL, &filename); - download_file(&file_url, &tailwind); - } - - let checksums = - Path::new(&env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env. (4)\n")) - .join("sha256sums.txt"); - - if !exists(&checksums).expect("\nError checking for checksums.\n") { - let sums_url = format!("{}sha256sums.txt", &TAILWIND_URL); - download_file(&sums_url, &checksums); - } - - let sums = File::open(&checksums).expect("\nError opening checksums.\n"); - let buf_reader = BufReader::new(sums); - - let mut expected_checksum = "".to_string(); - - for line in buf_reader.lines().map_while(Result::ok) { - let split_line = line.split_whitespace().collect::>(); - if format!("./{}", filename) == split_line[1] { - expected_checksum = split_line[0].into() - } - } - - let calculated_checksum = - sha256::try_digest(&tailwind).expect("\nError calculating checksum.\n"); - - println!("Expected Checksum: {}", expected_checksum); - println!("Calculated Checksum: {}", calculated_checksum); - - if expected_checksum == calculated_checksum { - println! {"Checksum match"}; - } else { - println! {"Checksum mismatch!"}; - let _idc_if_this_fails = remove_file(tailwind); - panic!("\nChecksum mismatch!\n"); - } - - if env::consts::FAMILY == "unix" { - Command::new("chmod") - .arg("+x") - .arg( - Path::new( - &env::var_os("OUT_DIR").expect("\nError reading OUT_DIR from env. (5)\n"), - ) - .join(&filename), - ) - .output() - .expect("\nError running chmod +x\n"); - } - - // Run downloaded tailwind - let _ = run_tailwind( - Some(&downloaded_tailwind_path), - &bundle_path, - &bundle_dark_path, - &singlestage_path, - &singlestage_dark_path, - ); + .unwrap() + .read_to_string(&mut buf); + let css_dark_raw = format!("{}{}", &css_raw, buf); + + ["", "_dark"].par_iter().for_each(|mode| { + let component_css = format!( + "{}{}", + match *mode { + "" => &css_raw, + _ => &css_dark_raw, + }, + features + .par_iter() + .map(|feature| { + let feature_flag = format!("CARGO_FEATURE_{}", feature.to_uppercase()); + if env::var(&feature_flag).is_ok() + && let Ok(mut file) = File::open( + &Path::new("src") + .join("components") + .join(feature) + .join("style") + .join(format!("{}{}.css", &feature, mode)), + ) + { + let mut buf = String::new(); + + if let Ok(read) = file.read_to_string(&mut buf) + && read > 0 + { + buf + } else { + "".into() + } + } else { + "".into() + } + }) + .collect::() + ); + let bundle_path = Path::new(&out_dir).join(format!("bundle{}.css", mode)); + let output_path = Path::new(&out_dir).join(format!("singlestage{}.css", mode)); + run_tailwind(&tailwind_path, &bundle_path, &output_path, component_css); + // TODO: Reduce dark style bloat + println!("Bundled css{}...", mode); + + base_styles.par_iter().for_each(|base_style| { + if env::var(&format!( + "CARGO_FEATURE_STYLE_{}", + base_style.to_uppercase() + )) + .is_ok() + { + let base_css = format!( + "{}{}", + match *mode { + "" => &css_raw, + _ => &css_dark_raw, + }, + features + .par_iter() + .map(|feature| { + let feature_flag = format!("CARGO_FEATURE_{}", feature.to_uppercase()); + if env::var(&feature_flag).is_ok() + && let Ok(mut file) = File::open( + &Path::new("src") + .join("components") + .join(feature) + .join("style") + .join("base") + .join(base_style) + .join(format!("{}{}.css", &feature, mode)), + ) + { + let mut buf = String::new(); + if let Ok(read) = file.read_to_string(&mut buf) + && read > 0 + { + buf + } else { + "".into() + } + } else { + "".into() + } + }) + .collect::() + ); + let bundle_path = + Path::new(&out_dir).join(format!("bundle_{}{}.css", base_style, mode)); + let output_path = + Path::new(&out_dir).join(format!("singlestage_{}{}.css", base_style, mode)); + run_tailwind(&tailwind_path, &bundle_path, &output_path, base_css); + // TODO: Reduce dark style bloat + // TODO: Reduce base style bloat + println!("Bundled base_{}{}...", base_style, mode); + } + }); + }); + + println!("Finished!"); } diff --git a/singlestage/src/components/theme_provider/main.css b/singlestage/src/components/theme_provider/main.css index a9e4f9f9..43b455a3 100644 --- a/singlestage/src/components/theme_provider/main.css +++ b/singlestage/src/components/theme_provider/main.css @@ -112,25 +112,6 @@ @apply antialiased bg-background overscroll-none text-foreground; } - @supports (font: -apple-system-body) and (-webkit-appearance: none) { - [data-wrapper] { - @apply min-[1800px]:border-t; - } - } - - .singlestage-ulist { - @apply list-disc ml-6 my-6; - } - - .singlestage-olist { - @apply list-decimal ml-6 my-6; - } - - a:active, - button:active { - @apply opacity-60 md:opacity-100; - } - .singlestage-scrollbar { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); diff --git a/singlestage/src/components/theme_provider/mod.rs b/singlestage/src/components/theme_provider/mod.rs index 4c8b1559..4d5a8331 100644 --- a/singlestage/src/components/theme_provider/mod.rs +++ b/singlestage/src/components/theme_provider/mod.rs @@ -1,9 +1,24 @@ +mod mode; +pub use mode::*; + #[allow(non_snake_case)] pub mod Theme; -mod mode; use crate::{CSS, CSS_DARK}; -pub use mode::*; +#[cfg(feature = "style_luma")] +use crate::{CSS_LUMA, CSS_LUMA_DARK}; +#[cfg(feature = "style_lyra")] +use crate::{CSS_LYRA, CSS_LYRA_DARK}; +#[cfg(feature = "style_maia")] +use crate::{CSS_MAIA, CSS_MAIA_DARK}; +#[cfg(feature = "style_mira")] +use crate::{CSS_MIRA, CSS_MIRA_DARK}; +#[cfg(feature = "style_nova")] +use crate::{CSS_NOVA, CSS_NOVA_DARK}; +#[cfg(feature = "style_sera")] +use crate::{CSS_SERA, CSS_SERA_DARK}; +#[cfg(feature = "style_vega")] +use crate::{CSS_VEGA, CSS_VEGA_DARK}; use leptos::prelude::*; use leptos_meta::Style; @@ -36,8 +51,26 @@ pub fn ThemeProviderInner( let context = ThemeProviderContext { theme, mode }; provide_context(context); + // TODO: Consider slicing up the base theme and merging everything to reduce css + // bloat/duplication view! { +